【问题标题】:Trying to split an int array into 2 and check if their averages can be equal using a recursive approach尝试将一个 int 数组拆分为 2,并使用递归方法检查它们的平均值是否相等
【发布时间】:2021-09-29 13:24:23
【问题描述】:

我的代码

    public boolean canSplitArraySameAverage(ArrayList<Integer> a, ArrayList<Integer> b) {

        double aSum = 0, bSum = 0;

        for (int it : a)   // aSum
            aSum = aSum + it;


        for (int it : b)   // bSum
            bSum = bSum + it;


        if ((!a.isEmpty() && !b.isEmpty()) && (bSum / b.size() == aSum / a.size())) //  Equal Average Possible
            return true;


        if (b.size() == 1)  //  Solution not possible, returning on reaching base case
            return false;


        for (int i = 0; i < b.size(); i++) { 

            a.add(b.remove(i));  // Transferring element from b to a

            // Creating Deep Copies
            ArrayList<Integer> newA = (ArrayList<Integer>) a.clone();
            ArrayList<Integer> newB = (ArrayList<Integer>) b.clone();

            if (canSplitArraySameAverage(newA, newB))
                return true;  // Early true return

        }

        System.out.println("Return :" + a);  
        return false;  //  Solution not possible, returning after exhausting for loop
    }

Logical Flow on how the code should execute

传递值 a[] 和 b[1 2 3 4]

当达到负的基本情况(b[] size = 1)时,我希望 a[] 的值如下

[1 2 3]
[1 2 4]
[1 2]
[1 3 2]
[1 3 4]
[1 3]
and so on

但是我的代码执行为

[1 2 3]
[1 2 4]
[1 3 2]
[1 3]
and terminates

我不确定问题出在哪里,我怀疑它与 return 语句有关。

【问题讨论】:

  • 你真的需要for循环吗?你不能只使用递归来处理它吗?尝试删除循环,如果 b 的长度 > 1,则只进行一次传输,然后再次调用该方法。
  • @nordenvall,我相信需要 for 循环才能正确进行分支,但是我可能是错的。我确实尝试删除 for 循环并将更新代码放入其中,但随后它只执行最左边的分支。输出变为 [1 2 3] [1 2] [1]

标签: java recursion arraylist dynamic-programming


【解决方案1】:

在克隆 ArrayLists 之前,您要将原始 List b 中的值添加到原始 List a

这意味着在第一次调用递归方法时,原始 List b 的第一个元素(在您的情况下为 1)将始终是 newA 的第一个元素。

解决这个问题的方法是在复制后转移元素:

      // Creating Deep Copies
      ArrayList<Integer> newA = (ArrayList<Integer>) a.clone();
      ArrayList<Integer> newB = (ArrayList<Integer>) b.clone();

      newA.add(newB.remove(i));

注意:由于您提前真正返回,并非所有无效案例都被访问


编辑:更多解释

假设您执行您的方法(来自问题)。这不会访问所有可能的组合并提供错误的结果。

之所以不检查所有组合,是因为您在复制之前将元素从b 转移到a

让我们看看for循环中发生了什么:

  • 使用i == 0 进入for 循环
  • 元素从(原始)b 转移到a
  • 现在 a = [1],b = [2,3,4]
  • 递归部分发生
  • i 增加。现在i == 1
  • 因为您从原始b 中删除了第一个元素,所以将添加的下一个元素是3(因为i == 1
  • 这会导致在进一步的步骤中永远不会检查 [1,4] [2,3] 的组合,并且您的方法会提供错误的结果。

这是你方法的固定代码:

public static boolean canSplitArraySameAverage(ArrayList<Integer> a, ArrayList<Integer> b) {

  double aSum = a.stream().reduce(0, (x,y) -> x+y);
  double bSum = b.stream().reduce(0, (x,y) -> x+y);
  
  if ((!a.isEmpty() && !b.isEmpty()) && (bSum / b.size() == aSum / a.size())) //  Equal Average Possible
    return true;
  
  if (b.size() == 1) //  Solution not possible, returning on reaching base case
    return false;
  
  for (int i = 0; i < b.size(); i++) {
    // Creating Deep Copies
    ArrayList<Integer> newA = (ArrayList<Integer>) a.clone();
    ArrayList<Integer> newB = (ArrayList<Integer>) b.clone();
  
    // Transferring element from newB to newA
    newA.add(newB.remove(i));
  
    if (canSplitArraySameAverage(newA, newB))
      return true;  // Early true retur
  }
  
  System.out.println("Return :" + a);
  return false;  //  Solution not possible, returning after exhausting for loop
}

当使用两个列表ab执行此方法时,其中a为空且b包含[1,2,3,4],输出为:

Return :[1, 2]
Return :[1, 3]

并且该方法的结果为“真”,因为拆分为 [1,4] 和 [2,3] 提供相同的平均值。
没有更多的输出,因为当b的大小为1时,则只返回false,不输出。

在不同的递归级别中检查的组合是:

Recursion-Level: 0; a = [], b = [1, 2, 3, 4]
Recursion-Level: 1; a = [1], b = [2, 3, 4]
Recursion-Level: 2; a = [1, 2], b = [3, 4]
Recursion-Level: 3; a = [1, 2, 3], b = [4]
Recursion-Level: 3; a = [1, 2, 4], b = [3]
Recursion-Level: 2; a = [1, 3], b = [2, 4]
Recursion-Level: 3; a = [1, 3, 2], b = [4]
Recursion-Level: 3; a = [1, 3, 4], b = [2]
Recursion-Level: 2; a = [1, 4], b = [2, 3]

【讨论】:

  • 在最初的通话中,我通过 [ ] 和 [1 2 3 4] 在第二次通话中它变成 [1] 和 [2 3 4] 然后 [1 2] 和 [3 4]依此类推,所以 newA 的值在每次调用中都会更新如果我要在更新之前打印“b”中的值,在更新之后打印“a”中的值,我会得到以下值 ```B :[1 2 3 4] A :[1] B :[2 3 4] A :[1 2] B :[3 4] A :[1 2 3] B :[3 4] A :[1 2 4] B :[2 3 4 ] A :[1 3] B :[2 4] A :[1 3 2] ``` 我也试过你的建议,它给了我 [1 2 3 4] [1 2 3] [1 3] 不知道是什么把它做成
  • @codingcredo 更新了我的答案,详细解释了为什么在克隆列表后转移元素很重要。如果您需要打印更多被检查的组合,您需要在第一个“return false;”中添加一个输出。在你的方法中。如果您需要打印所有组合,则需要过度考虑您的提前退出策略。
  • 非常感谢!最初我误解了你的解决方案,我最终转移了两次元素而不是重新定位线。我也非常感谢它的详细解释,它帮助我理解了我哪里出了问题以及你是如何纠正它的!
【解决方案2】:

如果你想打印你不能从 for 循环中返回的所有不可能的值,你将在第一个正数组合时打破它。

这对我有用(抱歉用 C# 重写了它,因为我没有可用的 java IDE atm 但我认为你掌握了这些变化):

private bool CanSplitArraySameAverage(List<int> a, List<int> b)
    {
        
        double aSum = 0, bSum = 0;

        foreach (var it in a)
        {
            aSum = aSum + it;
        }

        foreach (int it in b)
        {
            bSum = bSum + it;
        }

        if ((a.Any() && b.Any()) && (bSum / b.Count() == aSum / a.Count()))
        {
            return true;
        }

        bool canSplit = false;
        if(b.Count() != 1)
        {
            for (int i = 0; i < b.Count(); i++)
            {
                List<int> newA = new List<int>(a);
                List<int> newB = new List<int>(b);

                newA.Add(newB.ElementAt(i));  
                newB.RemoveAt(i);

                if (this.CanSplitArraySameAverage(newA, newB))
                {
                    canSplit = true;
                }

            }
        }

        if (!canSplit)
        {
            Console.WriteLine("Return :" + String.Join("-", a));
        }
        return canSplit;  
    }

给出输出:

Return :1-2-3
Return :1-2-4
Return :1-2
Return :1-3-2
Return :1-3-4
Return :1-3
Return :2-1-3
Return :2-1-4
Return :2-1
Return :2-4-1
Return :2-4-3
Return :2-4
Return :3-1-2
Return :3-1-4
Return :3-1
Return :3-4-1
Return :3-4-2
Return :3-4
Return :4-2-1
Return :4-2-3
Return :4-2
Return :4-3-1
Return :4-3-2
Return :4-3

没有 2-3 或 1-4 的组合

【讨论】:

  • 非常感谢@nordenvall,这很有帮助,是的,我能够跟进更改,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-22
  • 2015-05-25
  • 2015-08-24
  • 1970-01-01
  • 1970-01-01
  • 2021-11-09
  • 2015-01-15
相关资源
最近更新 更多