艰难的递归任务

时间:2016-02-18 09:02:29

标签: java arrays recursion

我一直在努力解决问题我试图解决作为测试准备的一部分,我想我可以使用你的帮助。 我需要编写一个布尔方法,它接受带有整数的数组(正数和负数),如果数组可以拆分为两个等于组,则返回true,即每个组的数量等于另一组。 例如,对于这个数组:

int[]arr = {-3, 5, 12, 14, -9, 13};

该方法将返回true,因为-3 + 5 + 14 = 12 + -9 + 13。

对于这个数组:

int[]arr = {-3, 5, -12, 14, -9, 13};

该方法将返回false,因为即使-3 + 5 + 14 + -12 = -9 + 13,等式中每一边的数字量都不等于。

对于数组:

int[]arr = {-3, 5, -12, 14, -9};

该方法将返回false,因为数组长度不均匀。

方法必须是递归的,允许重载,每个辅助方法也必须递归,并且我不需要担心复杂性。

我一直试图解决这个问题三个小时,我甚至没有代码可以显示,因为我所做的所有事情远非解决方案。

如果某人至少可以给我一些伪代码,那就太棒了。

非常感谢!

4 个答案:

答案 0 :(得分:4)

描述的问题是Partition问题的一个版本。首先请注意,您的公式相当于决定是否存在输入的子集,其总和高达所有元素总和的一半(需要为整数,否则实例无法求解,但这很容易校验)。基本上,在每个递归步骤中,要确定是否要将第一个数字选择到子集中,从而导致不同的递归调用。如果n表示元素的数量,则必须选择n/2(需要再次成为整数)。

Sum表示输入的总和,并假设续集中的Target := Sum / 2为积分。如果我们让

f(arr,a,count) := true
                  if there is a subset of arr summing up to a with
                  exactly count elements
                  false
                  otherwise

我们获得以下递归

f(arr,a,count) = (arr[0] == a && count == 1)
                 ||
                 (a == 0 && count == 0)
                 if arr contains only one element
                 f(arr\arr[0], a, count)
                 ||
                 f(arr\arr[0], a - arr[0], count -1)
                 if arr contains more than one element

其中||表示逻辑消除,&&表示逻辑结合,\表示删除元素。 非单例数组的两种情况对应于将arr的第一个元素选择为所需子集或其相对补码。请注意,在实际实现中,a实际上不会从数组中删除;一个起始索引,用作附加参数,将用0初始化,并在每次递归调用中增加,最终到达数组的末尾。

最后,f(arr,Target,n/2)会产生所需的值。

答案 1 :(得分:4)

你问过伪代码,但有时候把它写成Java也很简单明了。

此解决方案的一般想法是尝试将每个数字添加到等式的左侧或右侧。它跟踪递归中每一步的每一侧的计数和总和。评论中有更多解释:

class Balance {
  public static void main(String[] args) {
    System.out.println(balanced(-3, 5, 12, 14, -9, 13));   // true
    System.out.println(balanced(-3, 5, -12, 14, -9, 13));  // false
  }

  private static boolean balanced(int... nums) {
    // First check if there are an even number of nums.
    return nums.length % 2 == 0
        // Now start the recursion:
        && balanced(
            0, 0,  // Zero numbers on the left, summing to zero.
            0, 0,  // Zero numbers on the right, summing to zero.
            nums);
  }

  private static boolean balanced(
      int leftCount, int leftSum,
      int rightCount, int rightSum,
      int[] nums) {
    int idx = leftCount + rightCount;
    if (idx == nums.length) {
      // We have attributed all numbers to either side of the equation.
      // Now check if there are an equal number and equal sum on the two sides.
      return leftCount == rightCount && leftSum == rightSum;
    } else {
      // We still have numbers to allocate to one side or the other.
      return
          // What if I were to place nums[idx] on the left of the equation?
          balanced(
              leftCount + 1, leftSum + nums[idx],
              rightCount, rightSum,
              nums)
          // What if I were to place nums[idx] on the right of the equation?
          || balanced(
              leftCount, leftSum,
              rightCount + 1, rightSum + nums[idx],
              nums);
    }
  }
}

这只是第一个想法的解决方案。它是O(2 ^ n),对于大n来说显然相当慢,但是对于你给出的问题的大小来说这很好。

答案 2 :(得分:1)

您的策略应该是尽可能尝试所有组合。我将尝试记录我将如何实现这一目标。

注意我认为要求:使每个函数都使用递归有点困难,因为我会通过省略一些帮助函数来解决这个问题,这些函数使得代码更具可读性,所以在这种情况下我不会这样做。

通过递归,您总是希望向最终解决方案迈进,并检测您何时完成。所以我们在函数中需要两个部分:

  1. 递归步骤:为此,我们将获取输入集的第一个元素,并尝试将其添加到第一个集合时会发生什么,如果没有找到解决方案,我们将尝试当我们将它添加到第二组时会发生什么。
  2. 检测我们何时完成,即输入集为空时,在这种情况下我们要么找到了解决方案,要么我们没有找到解决方案。
  3. 我们的第一步中的一个技巧是,在获取集合的第一个元素后,如果我们尝试对剩余部分进行分区,我们不希望这两个集合相等,因为我们已经将第一个元素分配给其中一套。

    这导致遵循此策略的解决方案:

    public boolean isValidSet(MySet<int> inputSet, int sizeDifferenceSet1minus2)
    {
        if (inputSet.isEmpty())
        {
             return sizeDifferenceSet1minus2== 0;
        }
    
        int first = inptuSet.takeFirst();
        return isValidSet(inputSet.copyMinusFirst(), sizeDifferenceSet1minus2+ first)
                  || isValidSet(inputSet.copyMinusFirst(), sizeDifferenceSet1minus2+ -1 * first);
    }
    

    此代码需要一些您仍需要实现的帮助功能。

    它的作用是首先测试我们是否已达到结束条件,如果是,则返回此分区是否成功。如果我们仍然在集合中留下了元素,我们会尝试如果将它添加到第一个集合中会发生什么,然后将其添加到第二个集合时会发生什么。请注意,我们实际上并没有跟踪集合,我们只是跟踪集合1减2之间的大小差异,减少(但你可以传递两个集合)。

    另请注意,要使此实现起作用,您需要复制输入集而不是修改它!

    对于一些背景信息:这个问题被称为Partition Problem,它以NP完全着称(这意味着它可能无法有效地解决大量输入数据,但它非常很容易验证分区确实是一个解决方案。

答案 3 :(得分:0)

这是一个详细的例子:

public static void main(String[] args)
{
    System.out.println(balancedPartition(new int[] {-3, 5, 12, 14, -9, 13})); // true
    System.out.println(balancedPartition(new int[] {-3, 5, -12, 14, -9, 13})); // false
    System.out.println(balancedPartition(new int[] {-3, 5, -12, 14, -9})); // false
}

public static boolean balancedPartition(int[] arr)
{
    return balancedPartition(arr, 0, 0, 0, 0, 0, "", "");
}

private static boolean balancedPartition(int[] arr, int i, int groupA, int groupB, int counterA, int counterB, String groupAStr, String groupBStr)
{
    if (groupA == groupB && counterA == counterB && i == arr.length) // in case the groups are equal (also in the amount of numbers)
    {
        System.out.println(groupAStr.substring(0, groupAStr.length() - 3) + " = " + groupBStr.substring(0, groupBStr.length() - 3)); // print the groups
        return true;
    }
    
    if (i == arr.length) // boundaries checks
        return false;
    
    boolean r1 = balancedPartition(arr, i + 1, groupA + arr[i], groupB, counterA + 1, counterB, groupAStr + arr[i] + " + ", groupBStr); // try add to group 1
    boolean r2 = balancedPartition(arr, i + 1, groupA, groupB + arr[i], counterA, counterB + 1, groupAStr, groupBStr + arr[i] + " + "); // try add to group 2
    
    return r1 || r2;
}

输出:

-3 + 5 + 14 = 12 + -9 + 13 // 第一个数组的一个选项

12 + -9 + 13 = -3 + 5 + 14 // 第一个数组的另一个选项

true // 对于第一个数组

false // 对于第二个数组

false // 第三个数组