【问题标题】:Tough recursive task艰难的递归任务
【发布时间】:2016-05-30 08:26:32
【问题描述】:

作为考试准备的一部分,我一直在努力解决我试图解决的问题,我想我可以使用你的帮助。 我需要编写一个布尔方法,该方法采用整数(正数和负数)数组,如果数组可以拆分为两个 equals 组,则返回 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。

方法必须是递归的,允许重载,每个辅助方法也必须是递归的,我不用担心复杂度。

我已经尝试解决这个问题三个小时,我什至没有代码可以显示,因为我所做的所有事情都远未解决。

如果有人至少能给我一些伪代码,那就太好了。

非常感谢!

【问题讨论】:

  • 我认为你的例子有一个错误——你的意思是5而不是15arr的第一个定义中?
  • 提示:如果所有数字的交叉和不能被2整除,那么当所有个数字为积极的。 IE。 1+2+3+4+5+6 的交叉和为 21,因此您不能将其分成 2 组。但是 1,2,3,4,5,7 有 22 个,即 2+4+5 = 1+3+7。因此,作为第一步,您可以测试所有数字是否都是正数,如果是,请检查交叉和。 (我不确定这是否是一个聪明的方法,也许递归会以更好的方式解决它,从臀部拍摄)。
  • 是否有任何要求在 O(whatever) 中运行或者它只需要工作并且是递归的?
  • 哦,数组最长有多长?因为暴力破解和测试每种组合可能需要很长时间。
  • 你可以通过暴力破解它,只需说每个数字必须在等式的左边或右边;保留等式两边的数字的总和和计数,并在递归的每一步更新“左”或“右”数字对。它是 O(2^n),但这对于您给出的输入来说很小。

标签: java arrays recursion


【解决方案1】:

所描述的问题是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) 产生所需的值。

【讨论】:

  • 这是否考虑了平衡要求?
  • 天哪,它没有!我误读了问题的那一部分。这可以通过附加参数来解决。
  • 注意平衡也可以通过摆弄实例来实现:选择大于Sum绝对值的正M;将此添加到条目中,这也消除了输入中的零条目。
【解决方案2】:

您要求提供伪代码,但有时编写它就像编写 Java 一样简单明了。

此解决方案的总体思路是尝试将每个数字添加到等式的左侧或右侧。它在递归的每个步骤中跟踪每一侧的计数和总和。 cmets中的更多解释:

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,这显然相当慢,但对于您作为示例给出的问题的大小来说,这很好。

【讨论】:

  • 这段代码太完美了,甚至不需要使用辅助方法,而且干净简洁。男人你我的英雄
  • 能否请您给我一些关于如何解决此类问题的提示?过程是什么?你从考虑基本情况开始?关于什么是大问题,以及如何逐步缩小?当您尝试掌握递归时,它会变得非常困难。@AndyTurner
  • 这是一个相当广泛的问题。大多数情况下,它只需要熟悉和练习(我当然不会声称拥有一些深厚的 CS 知识......)。考虑如何将较大的问题分解为较小的问题(例如,而不是如何将 N 个数字分成两部分,而是如何将 1 个数字分配给其中一个分区);为了解决其余问题(例如,到目前为止两个分区中数字的总和/计数),您需要了解较小问题的解决方案。
  • @Andy Turner,尊敬的 Turner 先生,我们如何知道何时执行第一个命令递归 balanced(leftCount + 1, leftSum + nums[idx], rightCount, rightSum, nums) 以及何时执行第二个命令递归 balanced(leftCount, leftSum, rightCount + 1, rightSum + nums[idx], nums)?请解释一下。
【解决方案3】:

您的策略应该是尝试所有可能的组合。我将尝试记录我将如何做到这一点。

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

使用递归,您总是希望朝着最终解决方案前进,并检测何时完成。所以我们的函数需要两个部分:

  1. 递归步骤:我们将获取输入集合的第一个元素,并尝试将其添加到第一个集合中会发生什么,如果没有找到解决方案,我们将尝试当我们将其添加到第二组。
  2. 检测何时完成,即输入集为空时,在这种情况下,我们要么找到解决方案,要么没有找到解决方案。

我们第一步的一个技巧是,在获取集合的第一个元素之后,如果我们尝试分割剩余部分,我们不希望这两个集合不再相等,因为我们已经将第一个元素分配给了一个套。

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

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 完全而闻名(这意味着对于大量输入数据可能无法有效地解决它,但很容易解决验证分区确实是一个解决方案。

【讨论】:

    【解决方案4】:

    这是一个详细的例子:

    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 // 第三个数组

    【讨论】:

    • 这可能是一个不错的解决方案,但由于行长异常,难以阅读。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多