【问题标题】:Determine whether or not can an array of numbers can be divided into two arrays, with each array holding the same sum of numbers确定一个数字数组是否可以分成两个数组,每个数组包含相同的数字总和
【发布时间】:2014-11-16 00:16:51
【问题描述】:

下面是一个代码,用于确定一个数字数组是否可以分成两个数组,每个数组包含相同的数字总和。 例如:{1, 3 ,2, 6} 可以分为 {6} 和 {1,2,3},因此返回 true 而{1,5,7}不能一分为二,平衡数组,因此返回false

public boolean canBalance(int[] nums) {
    for (int i = 0; i < nums.length; i++) { 
       int sum = 0;
       for (int j = 0; j < i; j++) sum += nums[j];
       for (int j = i; j < nums.length; j++) sum -= nums[j];
       if (sum == 0) return true;    
    }    
    return false;
}

这是编码bat练习的公认答案,我特别不理解这篇文章:

for (int j = 0; j < i; j++) sum += nums[j];
for (int j = i; j < nums.length; j++) sum -= nums[j];

不进行迭代通常以 { 开始并以 } 结束? 如果 sum == 0 意味着它可以平衡怎么办? 我尝试在一张纸上记下它,数组为 {1,3,2,6},总和为 26,返回 false,很明显 {1,3,2,6} 应该返回 true。

我想我看错了代码,但我不知道是哪个。或者可能算法是假的,但在codingbat中被接受了

【问题讨论】:

  • 使用调试器。你会更好地理解它。
  • for 循环(以及其他分支结构)通常使用{ } 大括号来明确、正确或必要。但是当与 single 语句行一起使用时,它们并不是严格要求的。
  • 如果你的 for 循环中只有一个语句,你可以省略花括号。这同样适用于 do、while、if 和 else。但是,即使在这些情况下,使用 { 和 } 也被认为是良好的编码习惯。
  • for 循环只有一个语句。 var i 是要拆分的位置,其中的两个 for 循环添加(在第一个循环中)和减去(在第二个循环中)拆分位置两侧的数字 - 如果总数等于 0,则可以拆分为那一点。
  • 嗨,我不知道你的代码是正确的。您所描述的是“分区问题”。有关更多信息,请参阅此链接:Partition Problem

标签: java loops iteration


【解决方案1】:

两个for循环用于对数组的两部分进行加权,找到数组的数组平衡点

这样想:

你有一个空的天平,在外部 for 循环的第一次迭代中,i 为零。

来到第一个for循环,这里j是0,i是0i &lt; j是假的,所以它没有进入第一个for循环,它进入第二个for循环并减去所有总和中的数字。

从外部 for 循环的第二次迭代开始,它开始进入第一个 for 循环并 开始将数组的元素一一添加到总和中。

在图片中,就像从一个空的天平秤开始,将所有元素添加到第二个秤,然后将一个元素移动到第一个秤,像这样:

最后,如果和为零,则数组可以平衡,所以返回true。如果总和不为 0,则为不平衡。

中的值由循环平衡,如下所示:

i为0时外层for循环的迭代
循环 2 -> i(0) j(0) 减 1,总和为 -1
循环 2 -> i(0) j(1) 减 3,总和为 -4
循环 2 -> i(0) j(2) 减 2,总和为 -6
循环 2 -> i(0) j(3) 减 6,总和为 -12

当 i 为 1 时外部 for 循环的迭代
循环 1 -> i(1) j(0) 加 1,总和为 1
循环 2 -> i(1) j(1) 减 3,总和为 -2
循环 2 -> i(1) j(2) 减 2,总和为 -4
循环 2 -> i(1) j(3) 减 6,总和为 -10

当 i 为 2 时外部 for 循环的迭代
循环 1 -> i(2) j(0) 加 1,总和为 1
循环 1 -> i(2) j(1) 加 3,总和为 4
循环 2 -> i(2) j(2) 减 2,总和为 2
循环 2 -> i(2) j(3) 减 6,总和为 -4

i 为 3 时外部 for 循环的迭代
循环 1 -> i(3) j(0) 加 1,总和为 1
循环 1 -> i(3) j(1) 加 3,总和为 4
循环 1 -> i(3) j(2) 加 2,总和为 6
循环 2 -> i(3) j(3) 减 6,总和为 0

最终结果为真,因此数组可以平衡

代码:

public class Test {

    public static void main(String[] args) {
        int[] test = { 1, 3, 2, 6 };
        System.out.println("\nFinal result is "+canBalance(test));
    }

    public static boolean canBalance(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            System.out.println("\nIteration of outer for loop when i is " + i);
            int sum = 0;
            for (int j = 0; j < i; j++){
                sum += nums[j];
                System.out.println("Loop 1 -> i(" +i + ") j("+j + ") Add "+nums[j] + ", sum is "+sum+"       ");
            }
            for (int j = i; j < nums.length; j++){
                sum -= nums[j];
                System.out.println("Loop 2 -> i(" +i + ") j("+j + ") Subtract "+nums[j] + ", sum is "+sum+"       ");
            }
            if (sum == 0)
                return true;
        }
        return false;
    }
}

如果你想允许数组元素之间的混洗,你可以使用递归如下(cmets 是不言自明的)

public class Test {

    public static void main(String[] args) {
        int[] original = { 10, 2, 24, 32 };
        System.out.println(canDivideArray(original));
    }

    private static boolean canDivideArray(int[] originalArray) {
        int total = 0;

        for (int number : originalArray) {
            total += number;
        }

        // check if sum == 2x for any value of x
        if (total % 2 != 0) {
            return false;
        } else {
            // sum of each half array should be x
            total /= 2;
        }
        return isTotal(originalArray, originalArray.length, total);
    }

    private static boolean isTotal(int array[], int n, int total) {
        // successful termination condition
        if (total == 0) {
            return true;
        }
        
        // unsuccessful termination when elements have finished but total is not reached
        if (n == 0 && total != 0){
            return false;
        }

        // When last element is greater than total
        if (array[n - 1] > total)
            return isTotal(array, n - 1, total);

        //check if total can be obtained excluding the last element or including the last element 
        return isTotal(array, n - 1, total - array[n - 1]) || isTotal(array, n - 1, total); 
    }

}

【讨论】:

  • 是的,但是如果我尝试使用 1,3,2,6 总和不会为 0,而 1,3,2,6 可以分为两个平衡数组。是不是逻辑有问题?
  • 嗨,我尝试使用 1、3、2,但它不起作用。它返回一个 false,应该可以将其分为 {1,2} 和 {3}
  • 没有@Rei,那么你误解了数组平衡的概念。您可以搜索“数组的数组平衡点”并在任何搜索引擎中查看。平衡是指在数组中,如果有任何一点左侧的所有元素都等于该点右侧的所有元素之和,那么它就是平衡的。在这里,元素的顺序非常重要。 1,3,2 是不平衡的,因为按照这个顺序,没有一点可以打破数组并得到相等和的数组。
  • 数组被认为是一个线性排列,即一个序列,它不是一组混乱的随机元素。如果允许混杂,那么在这种情况下,这将是一个排列组合问题,而不是一个平衡问题。
  • 我想问一下排列和组合的事情,但我想这超出了这个线程的范围。谢谢,这回答了我的问题:D
【解决方案2】:

如果不允许对数组元素重新排序,我们只需要在给定数组中找到分割点。问题中的解决方案通过尝试所有可能的分割点并检查两部分的总和是否相等来做到这一点。它在输入数组的长度上具有二次方的努力。

请注意,很容易提出具有线性工作量的解决方案,例如以下 sn-p。它在数组的左侧和右侧建立元素的总和,在每一步中,通过添加一个数组元素来增加较小的总和。重复此过程直到各部分相遇。

这假定数组不包含任何负数。

public boolean canBalance(int[] nums) {
  int sumL = 0, sumR = 0;
  int l = -1, r = nums.length;
  while (r - l > 1) {
    if (sumL < sumR) {
      sumL += nums[++l];
    } else {
      sumR += nums[--r];
    }
  }
  return sumL == sumR;
}

【讨论】:

    【解决方案3】:

    这是该问题的递归解决方案,一种非递归解决方案可以使用辅助方法在 for 循环中获取索引 0 到当前索引的总和,而另一种可以从相同获取所有元素的总和当前索引到最后,这是有效的。现在,如果您想将元素放入数组并比较总和,首先找到标记溢出的点(索引),双方的总和相等,然后获取一个列表并将该索引之前的值添加到另一个列表中去在那个索引之后。

    这是我的(递归),它只确定是否有一个地方可以拆分数组,使得一侧的数字之和等于另一边的数字之和。担心 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);
    }
    

    //非递归

    public boolean canBalance(int[] nums) {
       for(int a = 0; a < nums.length; a++) {
          int leftSum = 0;
          int rightSum = 0; 
          for(int b = 0; b < a; b++) {
          leftSum += nums[b];
          }
          for(int c = a; c < nums.length; c++) {
          rightSum += nums[c];
          }
          if(leftSum == rightSum) {
          return true;
          }
       }
       return false;
    }
    

    【讨论】:

      【解决方案4】:

      我猜原始问题中的“划分”不允许对数组进行重新排序。因此,您只需在特定位置打破数组,我们将拥有数组的左侧和右侧。每边的数字应具有相同的总和。

      外循环的索引(i)是断点。

      for (int i = 0; i < nums.length; i++) {
      

      第一个内部循环对数组的左侧求和。

      for (int j = 0; j < i; j++) sum += nums[j];
      

      第二个内部循环从左侧数组的和中减去右侧的元素。

      for (int j = i; j < nums.length; j++) sum -= nums[j];
      

      如果最终结果为零,则表示左侧和右侧之和相同。 否则,继续外循环并检查其他中断位置,直到找到正确的中断位置。

      【讨论】:

        【解决方案5】:

        用 DP 解决方案解决这个问题。

        //创建一个二维数组并以自底向上的方式填充 //DP[totalsum / 2 + 1] [数组长度+]

        bool divisible(int arr[], int size)
        {
        int sum = 0;
        
        // Determine the sum 
        for (i = 0; i < size; i++) sum += arr[i];
        
        if (sum%2 != 0) return false;
        
        bool DP[sum/2+1][size+1];
        
        // initialize top row as true
        for (i = 0; i <= size; i++)
            DP[0][i] = true;
        
        // initialize leftmost column, except DP[0][0], as 0
        for (i = 1; i <= sum/2; i++)
            DP[i][0] = false;     
        
         // Fill the partition table in botton up manner 
         for (int i = 1; i <= sum/2; i++)  
         {
             for (int j = 1; j <= size; j++)  
             {
                 DP[i][j] = DP[i][j-1];
                 if (i >= arr[j-1])
                     DP[i][j] = DP[i][j] || DP[i - arr[j-1]][j-1];
             }        
         }    
         return DP[sum/2][size];
        }     
        

        【讨论】:

          【解决方案6】:

          正如@Alvin Bunk在评论中所说,您在问题中给出的答案本身并不是一个好的答案,即使数组中元素的顺序发生变化,它的工作方式也会有所不同。

          您应该查看此 wiki 以了解理论并实施它:http://en.wikipedia.org/wiki/Partition_problem

          【讨论】:

            【解决方案7】:

            我编写了一个独立的方法来对数组的各个部分求和,并在我的解决方案中使用它来获得我能看到的最简单的方法。如果有人有任何cmets,请加入。我感谢cmets。

            //Create a method that gets the sum from start to the iteration before end.
            public int sumArray (int[] nums, int start, int end)
            {
              //Create a sum int that tracks the sum.
              int returnSum = 0;
              //Add the values from start (inclusive) to end (exclusive). In other words i : [start, end)
              for (int i = start; i < end; i++)
              {
                returnSum += nums[i];
              }
              return returnSum;
            }
            
            //This is our main class.
            public boolean canBalance(int[] nums) {
              //If nums has an actual value, we can work with it.
              if (nums.length > 0)
              {
                //We check to see if there is a value that is equal by using the sumArray method.
                for (int i = 0; i < nums.length; i++)
                {
                  //If from [0,i) the value equals from [i, nums.length), we return true;
                  if (sumArray(nums, 0, i) == sumArray(nums, i, nums.length))
                  {
                    return true;
                  }
                }
                //If we finish the loop, and find nothing, we return false;
                return false;
              }
              //If there is no value, we return false.
              else
              {
                return false;
              }
            }
            

            【讨论】:

              【解决方案8】:

              如果在 Java 中的 for 语句之后的代码周围没有任何花括号,则下一行是唯一作为 for 语句的一部分处理的行。在这种情况下,for 就像一个调用下一​​行代码的函数,它是一个 for 语句,然后调用下一行代码。因此,第一个 for 调用第二个 for 来评估两侧是否相同,如果不是,则返回到第二个 for 继续递增直到完成,然后返回到第一个 for ,它递增,并调用第二个 for... 等等。不过,该代码似乎已部分损坏,因为它必须使所有数字都按数字顺序排列,而且它不会检查中间的任何内容。

              例如:
              {1, 2, 3, 1} //evaluates to true because 1-1=0, although it should be false
              {6, 2, 2, 3} //evaluates to true because 6-3-2=0, although it should be false
              {2, 3, 4, 6} //evaluates to true because 2+3-6=0, although it should be false

              【讨论】:

                猜你喜欢
                • 2019-04-04
                • 1970-01-01
                • 1970-01-01
                • 2021-01-03
                • 2021-08-03
                • 1970-01-01
                • 2015-12-19
                • 2012-09-28
                • 1970-01-01
                相关资源
                最近更新 更多