Как оптимально разделить массив на два подмножества так, чтобы сумма элементов в обоих была одинаковой, иначе дать ошибку?
как оптимально разделить массив на два подмассива, чтобы сумма элементов в обоих подмассивах была одинаковой, иначе дать ошибку?
Пример 1
дан массив
10, 20 , 30 , 5 , 40 , 50 , 40 , 15
его можно разделить как
10, 20, 30, 5, 40
и
50, 40, 15
каждый поддиапазон суммирует до 105.
Пример 2
10, 20, 30, 5, 40, 50, 40, 10
массив нельзя разделить на 2 массива равной суммы.
17 ответов
существует решение, которое включает динамическое программирование, которое выполняется в O(n*TotalSum)
, где n
- количество элементов в массиве и TotalSum
- это их общая сумма.
первая часть состоит в определении множества всех чисел, которые могут быть созданы путем добавления элементов в массив.
для массива размером n
мы будем называть это T(n)
,
T(n) = T(n-1) UNION { Array[n]+k | k is in T(n-1) }
(доказательство правильности-индукцией, как и в большинстве случаев рекурсивного функции.)
кроме того, запомните для каждой ячейки динамической матрицы элементы, которые были добавлены для ее создания.
простой анализ сложности покажет, что это делается в O(n*TotalSum)
.
после расчета T(n)
, поиск набора для элемента точно размер TotalSum / 2
.
если такой элемент существует, то элементы, которые его создали, вместе взятые, составляют TotalSum / 2
, и элементы, которые не являются частью его творения равный TotalSum / 2
(TotalSum - TotalSum / 2 = TotalSum / 2
).
это псевдополиномиальное решение. AFAIK, эта проблема не известна в P.
Это называется проблема раздела. Существуют оптимальные решения для некоторых частных случаев. Однако, в целом, это NP-полная проблема.
в своем общем варианте эта проблема накладывает 2 ограничения, и это можно сделать более простым способом.
- если раздел можно сделать только где-то по длине массива (мы не считаем элементы не по порядку)
- отрицательных чисел нет.
алгоритм, который тогда работает, может быть:
- есть 2 переменные, leftSum и rightSum
- начать увеличение leftSum слева, и rightSum справа от массива.
- попытаться исправить любой дисбаланс в нем.
следующий код делает выше:
public boolean canBalance(int[] nums) {
int leftSum = 0, rightSum = 0, i, j;
if(nums.length == 1)
return false;
for(i=0, j=nums.length-1; i<=j ;){
if(leftSum <= rightSum){
leftSum+=nums[i];
i++;
}else{
rightSum+=nums[j];
j--;
}
}
return (rightSum == leftSum);
}
вывод:
canBalance({1, 1, 1, 2, 1}) → true OK
canBalance({2, 1, 1, 2, 1}) → false OK
canBalance({10, 10}) → true OK
canBalance({1, 1, 1, 1, 4}) → true OK
canBalance({2, 1, 1, 1, 4}) → false OK
canBalance({2, 3, 4, 1, 2}) → false OK
canBalance({1, 2, 3, 1, 0, 2, 3}) → true OK
canBalance({1, 2, 3, 1, 0, 1, 3}) → false OK
canBalance({1}) → false OK
canBalance({1, 1, 1, 2, 1}) → true OK
конечно, если элементы могут быть объединены вне порядка, это превращается в проблему раздела со всей ее сложностью.
эта проблема говорит о том, что если массив может иметь два подмножества с их суммой элементов как то же самое. Так логическое значение должно быть возвращено. Я нашел эффективный алгоритм : Algo: Процедура Шаг 1: возьмите пустой массив в качестве контейнера, отсортируйте исходный массив и сохраните его в пустом. Шаг 2: Теперь возьмите два динамически распределяемых массива и выньте highest и 2nd highest из вспомогательного массива и сохраните его в двух подмассивах соответственно и удалите из вспомогательного массива. Шаг 3: сравните сумму элементов в подмножествах, меньшая сумма будет иметь шанс получить самый высокий оставшийся элемент в массиве, а затем удалить из контейнера. Шаг 4: петля через Шаг 3, пока контейнер не будет пуст. Шаг 5: сравните сумму двух подмножеств, если они одинаковы, верните true else false.
// сложность этой проблемы заключается в том, что может быть много комбинаций, но у этого algo есть один уникальный способ .
мне задали этот вопрос в интервью, и я дал ниже простое решение, так как раньше я не видел этой проблемы ни на одном веб-сайте.
скажем, массив A = {45,10,10,10,10,10,5} Тогда разделение будет при index = 1 (индекс на основе 0), так что у нас есть два равных набора сумм {45} и {10,10,10,10,5}
int leftSum = A[0], rightSum = A[A.length - 1];
int currentLeftIndex = 0; currentRightIndex = A.length - 1
/* Переместите два указателя индекса в середину массива до currentRightIndex != currentLeftIndex. Увеличьте leftIndex, если сумма левых элементов все еще меньше чем или равна сумме элементов в праве "rightIndex".В конце проверьте, если leftSum == rightSum. Если true, мы получили индекс как currentLeftIndex+1 (или просто currentRightIndex, так как currentRightIndex будет равен currentLeftIndex+1 в этом случае). */
while (currentLeftIndex < currentRightIndex)
{
if ( currentLeftIndex+1 != currentRightIndex && (leftSum + A[currentLeftIndex + 1) <=currentRightSum )
{
currentLeftIndex ++;
leftSum = leftSum + A[currentLeftIndex];
}
if ( currentRightIndex - 1 != currentLeftIndex && (rightSum + A[currentRightIndex - 1] <= currentLeftSum)
{
currentRightIndex --;
rightSum = rightSum + A[currentRightIndex];
}
}
if (CurrentLeftIndex == currentRightIndex - 1 && leftSum == rightSum)
PRINT("got split point at index "+currentRightIndex);
@Gal Subset-Sum является NP-полной и имеет алгоритм псевдополиномиального динамического программирования O(n*TotalSum). Но эта проблема не является NP-полной. Это частный случай, и на самом деле это можно решить в линейное время.
здесь мы ищем индекс, где мы можем разделить массив на две части с одинаковой суммой. Проверьте следующий код.
анализ: O (n), так как алгоритм только перебирает массив и не использует TotalSum.
public class EqualSumSplit {
public static int solution( int[] A ) {
int[] B = new int[A.length];
int[] C = new int[A.length];
int sum = 0;
for (int i=0; i< A.length; i++) {
sum += A[i];
B[i] = sum;
// System.out.print(B[i]+" ");
}
// System.out.println();
sum = 0;
for (int i=A.length-1; i>=0; i--) {
sum += A[i];
C[i] = sum;
// System.out.print(C[i]+" ");
}
// System.out.println();
for (int i=0; i< A.length-1; i++) {
if (B[i] == C[i+1]) {
System.out.println(i+" "+B[i]);
return i;
}
}
return -1;
}
public static void main(String args[] ) {
int[] A = {-7, 1, 2, 3, -4, 3, 0};
int[] B = {10, 20 , 30 , 5 , 40 , 50 , 40 , 15};
solution(A);
solution(B);
}
}
попробовал другое решение . кроме решений Wiki (проблема раздела).
static void subSet(int array[]) {
System.out.println("Input elements :" + Arrays.toString(array));
int sum = 0;
for (int element : array) {
sum = sum + element;
}
if (sum % 2 == 1) {
System.out.println("Invalid Pair");
return;
}
Arrays.sort(array);
System.out.println("Sorted elements :" + Arrays.toString(array));
int subSum = sum / 2;
int[] subSet = new int[array.length];
int tmpSum = 0;
boolean isFastpath = true;
int lastStopIndex = 0;
for (int j = array.length - 1; j >= 0; j--) {
tmpSum = tmpSum + array[j];
if (tmpSum == subSum) { // if Match found
if (isFastpath) { // if no skip required and straight forward
// method
System.out.println("Found SubSets 0..." + (j - 1) + " and "
+ j + "..." + (array.length - 1));
} else {
subSet[j] = array[j];
array[j] = 0;
System.out.println("Found..");
System.out.println("Set 1" + Arrays.toString(subSet));
System.out.println("Set 2" + Arrays.toString(array));
}
return;
} else {
// Either the tmpSum greater than subSum or less .
// if less , just look for next item
if (tmpSum < subSum && ((subSum - tmpSum) >= array[0])) {
if (lastStopIndex > j && subSet[lastStopIndex] == 0) {
subSet[lastStopIndex] = array[lastStopIndex];
array[lastStopIndex] = 0;
}
lastStopIndex = j;
continue;
}
isFastpath = false;
if (subSet[lastStopIndex] == 0) {
subSet[lastStopIndex] = array[lastStopIndex];
array[lastStopIndex] = 0;
}
tmpSum = tmpSum - array[j];
}
}
}
Я испытал. ( Он хорошо работает с положительным числом больше 0) пожалуйста, дайте мне знать, если любое лицо вопрос.
это рекурсивное решение проблемы, одно нерекурсивное решение может использовать вспомогательный метод для получения суммы индексов 0 к текущему индексу в цикле for, а другое может получить сумму всех элементов из того же текущего индекса до конца, который работает. Теперь, если вы хотите получить элементы в массив и сравнить сумму, сначала найдите точку (индекс), которая отмечает разлитую, где сумма обеих сторон равна, затем получите список и добавьте значения перед этим индексом и другим список, чтобы пойти после этого индекса.
вот моя (рекурсия), которая определяет только, есть ли место для разделения массива так, чтобы сумма чисел на одной стороне была равна сумме чисел на другой стороне. Беспокойство об indexOutOfBounds, которое может легко произойти в рекурсии, небольшая ошибка может оказаться фатальной и дать много исключений и ошибок.
public boolean canBalance(int[] nums) {
return (nums.length <= 1) ? false : canBalanceRecur(nums, 0);
}
public boolean canBalanceRecur(int[] nums, int index){ //recursive version
if(index == nums.length - 1 && recurSumBeforeIndex(nums, 0, index)
!= sumAfterIndex(nums, index)){ //if we get here and its still bad
return false;
}
if(recurSumBeforeIndex(nums, 0, index + 1) == sumAfterIndex(nums, index + 1)){
return true;
}
return canBalanceRecur(nums, index + 1); //move the index up
}
public int recurSumBeforeIndex(int[] nums, int start, int index){
return (start == index - 1 && start < nums.length)
? nums[start]
: nums[start] + recurSumBeforeIndex(nums, start + 1, index);
}
public int sumAfterIndex(int[] nums, int startIndex){
return (startIndex == nums.length - 1)
? nums[nums.length - 1]
: nums[startIndex] + sumAfterIndex(nums, startIndex + 1);
}
нашли решение здесь
package sort;
import java.util.ArrayList;
import java.util.List;
public class ArraySumSplit {
public static void main (String[] args) throws Exception {
int arr[] = {1 , 2 , 3 , 4 , 5 , 5, 1, 1, 3, 2, 1};
split(arr);
}
static void split(int[] array) throws Exception {
int sum = 0;
for(int n : array) sum += n;
if(sum % 2 == 1) throw new Exception(); //impossible to split evenly
List<Integer> firstPart = new ArrayList<Integer>();
List<Integer> secondPart = new ArrayList<Integer>();
if(!dfs(0, sum / 2, array, firstPart, secondPart)) throw new Exception(); // impossible to split evenly;
//firstPart and secondPart have the grouped elements, print or return them if necessary.
System.out.print(firstPart.toString());
int sum1 = 0;
for (Integer val : firstPart) {
sum1 += val;
}
System.out.println(" = " + sum1);
System.out.print(secondPart.toString());
int sum2 = 0;
for (Integer val : secondPart) {
sum2 += val;
}
System.out.println(" = " + sum2);
}
static boolean dfs(int i, int limit, int[] array, List<Integer> firstPart, List<Integer> secondPart) {
if( limit == 0) {
for(int j = i; j < array.length; j++) {
secondPart.add(array[j]);
}
return true;
}
if(limit < 0 || i == array.length) {
return false;
}
firstPart.add(array[i]);
if(dfs(i + 1, limit - array[i], array, firstPart, secondPart)) return true;
firstPart.remove(firstPart.size() - 1);
secondPart.add(array[i]);
if(dfs(i + 1, limit, array, firstPart, secondPart)) return true;
secondPart.remove(secondPart.size() - 1);
return false;
}
}
во - первых, если элементы являются целыми числами, проверьте, что сумма равномерно делится на два-если это не успех невозможен.
Я бы установил проблему как двоичное дерево, с уровнем 0, решающим, в какой элемент 0 входит, уровень 1, решающий, в какой элемент 1 входит и т. д. В любой момент, если сумма одного набора равна половине общей суммы, Вы сделали-успех. В любое время, если сумма одного набора больше половины общей суммы, это под-дерево является сбоем, и вы должны создать резервную копию. На эта точка является проблемой обхода дерева.
public class Problem1 {
public static void main(String[] args) throws IOException{
Scanner scanner=new Scanner(System.in);
ArrayList<Integer> array=new ArrayList<Integer>();
int cases;
System.out.println("Enter the test cases");
cases=scanner.nextInt();
for(int i=0;i<cases;i++){
int size;
size=scanner.nextInt();
System.out.println("Enter the Initial array size : ");
for(int j=0;j<size;j++){
System.out.println("Enter elements in the array");
int element;
element=scanner.nextInt();
array.add(element);
}
}
if(validate(array)){
System.out.println("Array can be Partitioned");}
else{
System.out.println("Error");}
}
public static boolean validate(ArrayList<Integer> array){
boolean flag=false;
Collections.sort(array);
System.out.println(array);
int index=array.size();
ArrayList<Integer> sub1=new ArrayList<Integer>();
ArrayList<Integer> sub2=new ArrayList<Integer>();
sub1.add(array.get(index-1));
array.remove(index-1);
index=array.size();
sub2.add(array.get(index-1));
array.remove(index-1);
while(!array.isEmpty()){
if(compareSum(sub1,sub2)){
index=array.size();
sub2.add(array.get(index-1));
array.remove(index-1);
}
else{
index=array.size();
sub1.add(array.get(index-1));
array.remove(index-1);
}
}
if(sumOfArray(sub1).equals(sumOfArray(sub2)))
flag=true;
else
flag=false;
return flag;
}
public static Integer sumOfArray(ArrayList<Integer> array){
Iterator<Integer> it=array.iterator();
Integer sum=0;
while(it.hasNext()){
sum +=it.next();
}
return sum;
}
public static boolean compareSum(ArrayList<Integer> sub1,ArrayList<Integer> sub2){
boolean flag=false;
int sum1=sumOfArray(sub1);
int sum2=sumOfArray(sub2);
if(sum1>sum2)
flag=true;
else
flag=false;
return flag;
}
}
// жадный подход //
:
Шаг 1) Разделите массив на два
Шаг 2) Если сумма равна, split является полным
Шаг 3) Замените один элемент из array1 на array2, руководствуясь четырьмя правилами:
Если сумма элементов в array1 меньше суммы элементов в array2
Правило1:
Найдите число в array1, которое меньше числа в array2 таким образом, что замена
эти элементы не увеличивают сумму array1 за пределами ожидаемая сумма. Если найдено, замените
элементы и возвращение.
Правило 2:
Если Rule1 не выполняется, найдите число в array1, которое больше числа в array2 в
таким образом, разница между любыми двумя числами в array1 и array2 не меньше, чем
разница между этими двумя числами.
Иначе!--3-->
Rule3:
Найдите число в array1, которое больше числа в array2 таким образом, чтобы меняя эти
элементы, не уменьшайте сумму array1 сверх ожидаемой суммы. Если найдено, замените
элементы и возвращения.
Rule4:
Если Rule3 не выполняется, найдите число в array1, которое меньше числа в array2 в
таким образом, разница между любыми двумя числами в array1 и array2 не меньше, чем
разница между этими двумя числами.
Шаг 5) перейдите к Шагу 2, пока своп не приведет к массив с тем же набором элементов, которые уже возникли
Setp 6)если повторение происходит, этот массив нельзя разделить на две половины с равной суммой. Текущий набор массивов или набор, который был сформирован непосредственно перед этим повторением, должен быть лучшим разделением массива.
Примечание: используемый подход заключается в замене элемента из одного массива на другой таким образом, чтобы результирующая сумма была как можно ближе к ожидаемой сумме.
программа java доступна на Java-Код
пожалуйста, попробуйте это и дайте мне знать, если не работает. Надеюсь, это поможет вам.
static ArrayList<Integer> array = null;
public static void main(String[] args) throws IOException {
ArrayList<Integer> inputArray = getinputArray();
System.out.println("inputArray is " + inputArray);
Collections.sort(inputArray);
int totalSum = 0;
Iterator<Integer> inputArrayIterator = inputArray.iterator();
while (inputArrayIterator.hasNext()) {
totalSum = totalSum + inputArrayIterator.next();
}
if (totalSum % 2 != 0) {
System.out.println("Not Possible");
return;
}
int leftSum = inputArray.get(0);
int rightSum = inputArray.get(inputArray.size() - 1);
int currentLeftIndex = 0;
int currentRightIndex = inputArray.size() - 1;
while (leftSum <= (totalSum / 2)) {
if ((currentLeftIndex + 1 != currentRightIndex)
&& leftSum != (totalSum / 2)) {
currentLeftIndex++;
leftSum = leftSum + inputArray.get(currentLeftIndex);
} else
break;
}
if (leftSum == (totalSum / 2)) {
ArrayList<Integer> splitleft = new ArrayList<Integer>();
ArrayList<Integer> splitright = new ArrayList<Integer>();
for (int i = 0; i <= currentLeftIndex; i++) {
splitleft.add(inputArray.get(i));
}
for (int i = currentLeftIndex + 1; i < inputArray.size(); i++) {
splitright.add(inputArray.get(i));
}
System.out.println("splitleft is :" + splitleft);
System.out.println("splitright is :" + splitright);
}
else
System.out.println("Not possible");
}
public static ArrayList<Integer> getinputArray() {
Scanner scanner = new Scanner(System.in);
array = new ArrayList<Integer>();
int size;
System.out.println("Enter the Initial array size : ");
size = scanner.nextInt();
System.out.println("Enter elements in the array");
for (int j = 0; j < size; j++) {
int element;
element = scanner.nextInt();
array.add(element);
}
return array;
}
}
public boolean splitBetween(int[] x){
int sum=0;
int sum1=0;
if (x.length==1){
System.out.println("Not a valid value");
}
for (int i=0;i<x.length;i++){
sum=sum+x[i];
System.out.println(sum);
for (int j=i+1;j<x.length;j++){
sum1=sum1+x[j];
System.out.println("SUm1:"+sum1);
}
if(sum==sum1){
System.out.println("split possible");
System.out.println("Sum: " +sum +" Sum1:" + sum1);
return true;
}else{
System.out.println("Split not possible");
}
sum1=0;
}
return false;
}
package PACKAGE1;
import java.io.*;
import java.util.Arrays;
public class programToSplitAnArray {
public static void main(String args[]) throws NumberFormatException,
IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("enter the no. of elements to enter");
int n = Integer.parseInt(br.readLine());
int x[] = new int[n];
int half;
for (int i = 0; i < n; i++) {
x[i] = Integer.parseInt(br.readLine());
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum = sum + x[i];
}
if (sum % 2 != 0) {
System.out.println("the sum is odd and cannot be divided");
System.out.println("The sum is " + sum);
}
else {
boolean div = false;
half = sum / 2;
int sum1 = 0;
for (int i = 0; i < n; i++) {
sum1 = sum1 + x[i];
if (sum1 == half) {
System.out.println("array can be divided");
div = true;
break;
}
}
if (div == true) {
int t = 0;
int[] array1 = new int[n];
int count = 0;
for (int i = 0; i < n; i++) {
t = t + x[i];
if (t <= half) {
array1[i] = x[i];
count++;
}
}
array1 = Arrays.copyOf(array1, count);
int array2[] = new int[n - count];
int k = 0;
for (int i = count; i < n; i++) {
array2[k] = x[i];
k++;
}
System.out.println("The first array is ");
for (int m : array1) {
System.out.println(m);
}
System.out.println("The second array is ");
for (int m : array2) {
System.out.println(m);
}
} else {
System.out.println("array cannot be divided");
}
}
}
}
плохая жадная эвристика для решения этой проблемы: попробуйте отсортировать список от наименьшего до наибольшего и разделить этот список на два, имея list1 = нечетные элементы и list2 = четные элементы.
очень простое решение с рекурсией
public boolean splitArray(int[] nums){
return arrCheck(0, nums, 0);
}
public boolean arrCheck(int start, int[] nums, int tot){
if(start >= nums.length) return tot == 0;
if(arrCheck(start+1, nums, tot+nums[start])) return true;
if(arrCheck(start+1, nums, tot-nums[start])) return true;
return false;
}