Comment diviser de manière optimale un tableau en deux sous-tableaux de manière à ce que la somme des éléments des deux sous-tableaux soit identique, sinon donner une erreur?
Étant donné le tableau
10, 20 , 30 , 5 , 40 , 50 , 40 , 15
Il peut être divisé en
10, 20, 30, 5, 40
et
50, 40, 15
Chaque sous-tableau représente 105.
10, 20, 30, 5, 40, 50, 40, 10
Le tableau ne peut pas être divisé en 2 tableaux d'une somme égale.
Il existe une solution, impliquant une programmation dynamique, qui s'exécute dans O(n*TotalSum)
, où n
est le nombre d'éléments du tableau et TotalSum
est leur somme totale.
La première partie consiste à calculer l'ensemble de tous les nombres pouvant être créés en ajoutant des éléments au tableau.
Pour un tableau de taille n
, nous appellerons ceci T(n)
,
T(n) = T(n-1) UNION { Array[n]+k | k is in T(n-1) }
(La preuve de l'exactitude est par induction, comme dans la plupart des cas de fonctions récursives.)
En outre, rappelez-vous, pour chaque cellule de la matrice dynamique, les éléments ajoutés afin de la créer.
Une simple analyse de complexité montrera que cela est fait dans O(n*TotalSum)
.
Après avoir calculé T(n)
, recherchez dans l’ensemble un élément ayant exactement la taille de TotalSum / 2
.
Si un tel élément existe, les éléments qui l'ont créé, ajoutés ensemble, correspondent à TotalSum / 2
et les éléments qui ne faisaient pas partie de sa création sont également égaux à TotalSum / 2
(TotalSum - TotalSum / 2 = TotalSum / 2
).
C'est une solution pseudo-polynomiale. Autant que je sache, ce problème n'est pas connu pour être dansP.
Cela s'appelle problème de partition . Il existe des solutions optimales pour certains cas particuliers. Cependant, en général, c'est un problème NP-complet.
Dans sa variante commune, ce problème impose 2 contraintes et cela peut être fait de manière plus simple.
L'algorithme qui fonctionne alors pourrait être:
Le code suivant fait ce qui précède:
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);
}
Le résultat:
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
Bien entendu, si les éléments peuvent être combinés dans le désordre, il se transforme en problème de partition avec toute sa complexité.
J'ai essayé une solution différente. autres que les solutions Wiki (problème de partition).
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];
}
}
}
J'ai testé. (Cela fonctionne bien avec un nombre positif supérieur à 0) s'il vous plaît laissez-moi savoir si l'un des visage problème.
Ceci est une solution récursive au problème, une solution non récursive pourrait utiliser une méthode d'assistance pour obtenir la somme des index 0 d'un index courant dans une boucle for et une autre solution pour obtenir la somme de tous les éléments du même index actuel. la fin, qui fonctionne. Maintenant, si vous voulez obtenir les éléments dans un tableau et comparer la somme, commencez par trouver le point (index) qui marque le déversé où la somme des deux côtés est égale, puis obtenez une liste et ajoutez les valeurs avant cet index et une autre liste après cet index.
Voici le mien (récursion), qui détermine uniquement s'il existe un emplacement pour fractionner le tableau, de sorte que la somme des nombres d'un côté soit égale à la somme des nombres de l'autre côté. Inquiétude vis-à-vis d'indexOutOfBounds, qui peut facilement se produire en récursion, une légère erreur peut s'avérer fatale et générer de nombreuses exceptions et erreurs.
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);
}
Ce problème indique que si un tableau peut avoir deux sous-tableaux avec leur somme d'éléments identiques . Une valeur booléenne doit donc être renvoyée . J'ai trouvé un algorithme efficace: Algo: Procedure Step. 1: Prenez un tableau vide en tant que conteneur, triez le tableau initial et conservez-le dans le vide ..___ Étape 2: prenez maintenant deux tableaux allouables dynamiquement et sortez le plus élevé et le deuxième plus élevé du tableau auxiliaire Etape 3: Comparez la somme des éléments dans les sous-tableaux, la plus petite somme aura la chance d'extraire le plus haut élément restant dans le tableau, puis de la supprimer du conteneur . Étape 4: Boucle par l’étape 3 jusqu’à ce que le conteneur soit vide .. Étape 5: Comparez la somme de deux sous-tableaux, s’ils sont identiques, renvoyez true, sinon false.
// La complexité de ce problème réside dans le fait qu’il peut y avoir de nombreuses combinaisons possibles, mais cet algorithme a une manière unique.
On m'a posé cette question lors d'un entretien et j'ai donné ci-dessous une solution simple, car je n'avais pas vu ce problème sur aucun site Web auparavant.
Disons que le tableau A = {45,10,10,10,10,5} Ensuite, la division sera à index = 1 (index basé sur 0) de sorte que nous ayons deux ensembles de somme égaux {45} et { 10,10,10,10,5}
int leftSum = A[0], rightSum = A[A.length - 1];
int currentLeftIndex = 0; currentRightIndex = A.length - 1
/*Déplacez les deux pointeurs d'index vers le milieu du tableau jusqu'à currentRightIndex! = CurrentLeftIndex. Augmentez leftIndex si la somme des éléments de gauche est toujours inférieure ou égale à la somme des éléments situés à droite de 'rightIndex'. À la fin, vérifiez si leftSum == rightSum. Si true, nous avons obtenu l'index comme currentLeftIndex + 1 (ou simplement currentRightIndex, car currentRightIndex sera égal à currentLeftIndex + 1 dans ce cas) . * /
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);
Le problème @Gal Subset-Sum est NP-Complete et possède un algorithme de programmation dynamique pseudo-polynomial O (n * TotalSum). Mais ce problème n'est pas NP-Complete. Il s’agit d’un cas spécial qui peut être résolu en temps linéaire.
Nous cherchons ici un index permettant de scinder le tableau en deux parties avec la même somme . Vérifier le code suivant.
Analyse: O (n), car l’algorithme itère uniquement dans le tableau et n’utilise pas 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);
}
}
Solution trouvée ici
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;
}
}