简体   繁体   English

艰难的递归任务

[英]Tough recursive task

I've been struggle with question I'm trying to solve as part of test preparation, and I thought I could use your help. 作为测试准备的一部分,我一直在努力解决问题,我想我可以使用你的帮助。 I need to write a Boolean method that takes array with integers (positive and negative), and return true if the array can be split to two equals groups, that the amount of every group's numbers is equals to the other group. 我需要编写一个布尔方法,它接受带有整数的数组(正数和负数),如果数组可以拆分为两个等于组,则返回true,即每个组的数量等于另一个数组。 For exmaple, for this array: 例如,对于这个数组:

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

The method will return true, since -3 + 5 + 14 = 12 + -9 + 13. 该方法将返回true,因为-3 + 5 + 14 = 12 + -9 + 13。

For this array: 对于这个数组:

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

The method will return false since even though -3 + 5 + 14 + -12 = -9 + 13, the amount of numbers in every side of the equation isn't equals. 该方法将返回false,因为即使-3 + 5 + 14 + -12 = -9 + 13,等式的每一侧的数字量也不等于。

For the array: 对于数组:

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

The method will return false since array length isn't even. 该方法将返回false,因为数组长度不均匀。

The method must be recursive, overloading is allowed, every assist method must be recursive too, and I don't need to worry about complexity. 该方法必须是递归的,允许重载,每个辅助方法也必须递归,我不需要担心复杂性。

I've been trying to solve this for three hours, I don't even have a code to show since all the things I did was far from the solution. 我一直试图解决这个问题三个小时,我甚至都没有显示代码,因为我所做的所有事情都远非解决方案。

If someone can at least give me some pseudo code it will be great. 如果有人至少可以给我一些伪代码,那就太棒了。

Thank you very much! 非常感谢你!

The problem described is a version of the Partition problem. 描述的问题是分区问题的一个版本。 First note that your formulation is equivalent to deciding whether there is a subset of the input which sums up to half of the sum of all elements (which is required to be an integral number, otherwise the instance cannot be solved, but this is easy to check). 首先请注意,您的公式相当于决定是否存在输入的子集,其总和高达所有元素总和的一半(需要为整数,否则实例无法求解,但这很容易校验)。 Basically, in each recursive step, it is to be decided whether the first number is to be selected into the subset or not, resulting in different recursive calls. 基本上,在每个递归步骤中,要确定是否要将第一个数字选择到子集中,从而导致不同的递归调用。 If n denotes the number of elements, there must be n/2 (which is required to be integral again) items selected. 如果n表示元素的数量,则必须有n/2 (需要再次积分)项目。

Let Sum denote the sum of the input and let Target := Sum / 2 which in the sequel is assumed to be integral. Sum表示输入的总和,并且令Target := Sum / 2 ,其在后续中被假定为积分。 if we let 如果我们让

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

we obtain the following recursion 我们获得以下递归

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

where || 在哪里|| denotes logical disjuction, && denoted logical conjunction and \\ denotes removal of an element. 表示逻辑消除, &&表示逻辑连接, \\表示元素的删除。 The two cases for a non-singleton array correspond to chosing the first element of arr into the desired subset or its relative complement. 非单例数组的两种情况对应于将arr的第一个元素选择为所需子集或其相对补码。 Note that in an actual implementation, a would not be actually removed from the array; 请注意,在实际实施中, a不会真正从数组中删除; a starting index, which is used as an additional argument, would be initialized with 0 and increased in each recursive call, eventually reaching the end of the array. 一个起始索引(用作附加参数)将初始化为0并在每次递归调用中增加,最终到达数组的末尾。

Finally, f(arr,Target,n/2) yields the desired value. 最后, f(arr,Target,n/2)产生所需的值。

You asked for pseudocode, but sometimes it's just as easy and clear to write it as Java. 您要求使用伪代码,但有时将其编写为Java也同样简单明了。

The general idea of this solution is to try adding each number to either the left or the right of the equation. 该解决方案的一般思想是尝试将每个数字添加到等式的左侧或右侧。 It keeps track of the count and sum on each side at each step in the recursion. 它跟踪递归中每一步的每一侧的计数和总和。 More explanation in comments: 评论中有更多解释:

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);
    }
  }
}

This is just a first idea solution. 这只是第一个想法的解决方案。 It's O(2^n), which is obviously rather slow for large n , but it's fine for the size of problems you have given as examples. 它是O(2 ^ n),对于大n来说显然相当慢,但是对于你给出的问题的大小来说这很好。

Your strategy for this should be to try all combinations possible. 您的策略应该是尝试所有可能的组合。 I will try to document how I would go about to get to this. 我将尝试记录如何实现这一目标。

NOTE that I think the requirement: make every function use recursion is a bit hard, because I would solve that by leaving out some helper functions that make the code much more readable, so in this case I wont do it like that. 注意我认为要求:让每个函数都使用递归有点困难,因为我会通过省略一些帮助函数来解决这个问题,这些函数使得代码更具可读性,所以在这种情况下我不会这样做。

With recursion you always want to make progression towards a final solution, and detect when you are done. 通过递归,您总是希望向最终解决方案发展,并检测您何时完成。 So we need two parts in our function: 所以我们在函数中需要两个部分:

  1. The recursive step: for which we will take the first element of the input set, and try what happens if we add it to the first set, and if that doesn't find a solution we'll try what happens when we add it to the second set. 递归步骤:为此我们将获取输入集的第一个元素,并尝试如果我们将它添加到第一个集合会发生什么,如果找不到解决方案,我们将尝试将它添加到第二集。
  2. Detect when we are done, that is when the input set is empty, in that case we either have found a solution or we have not. 检测我们何时完成,即输入集为空时,在这种情况下,我们要么找到了解决方案,要么我们没有找到解决方案。

A trick in our first step is that after taking the first element of our set, if we try to partition the remainder, we don't want the 2 sets being equal anymore, because we already assigned the first element to one of the sets. 我们的第一步中的一个技巧是,在获取集合的第一个元素之后,如果我们尝试对余数进行分区,我们不希望这两个集合相等,因为我们已经将第一个元素分配给其中一个集合。

This leads to a solution that follows this strategy: 这导致了遵循此策略的解决方案:

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);
}

This code requires some help functions that you will still need to implement. 此代码需要一些您仍需要实现的帮助功能。

What it does is first test if we have reached the end condition, and if so returns if this partition is successful. 它的作用是首先测试我们是否已达到结束条件,如果是,则返回此分区是否成功。 If we still have elements left in the set, we try what happens if we add it to the first set and then what happens when adding it to the second set. 如果我们仍然在集合中留下了元素,我们会尝试如果将它添加到第一个集合中会发生什么,然后将其添加到第二个集合时会发生什么。 Note that we don't actually keep track of the sets, we just keep track of the size difference between set 1 minus 2, decreasing the (but instead you could pass along both sets). 请注意,我们实际上并没有跟踪集合,我们只是跟踪集合1减去2之间的大小差异,减少(但您可以传递两个集合)。

Also note that for this implementation to work, you need to make copies of the input set and not modify it! 另请注意,要使此实现起作用,您需要复制输入集而不是修改它!

For some background information: This problem is called the Partition Problem , which is famous for being NP-complete (which means it probably is not possible to solve it efficiently for large amounts of input data, but it is very easy to verify that a partitioning is indeed a solution. 对于一些背景信息:这个问题被称为分区问题 ,它以NP完全着称(这意味着它可能无法有效地解决大量输入数据,但很容易验证分区确实是一个解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ForkJoinPool中递归任务和递归操作之间的区别 - Difference between recursive task and recursive action in ForkJoinPool 下面的递归任务的实现是否正确? - is the implementation of the Recursive Task below correct? 在 Loom 中,我可以为 Recursive[Action/Task] 使用虚拟线程吗? - In Loom, can I use virtual threads for Recursive[Action/Task]? 是否存在递归Ant任务以从外部文件恢复属性? - Does a recursive Ant task exist to recover properties from external file? Eclipse 中的 Maven 构建任务问题 - StackOverflow 和 DeferredTypeMap 的递归调用? - Maven build task issue in Eclipse - StackOverflow and recursive call at DeferredTypeMap? 尝试使用 Java 中的递归任务(分叉和连接)计算 (3*3)^2 时出错 - Error when trying to calculate (3*3)^2 using Recursive Task (Fork and Join) in Java WebTestclient 正在抛出 Null 异常艰难的@Autowire 在那里 - WebTestclient is throwing Null Exception tough @Autowire is there 我在这个 for 循环示例中遇到了困难 - I'm having a tough time with this for loop example 这是递归的还是不递归的? - Is this recursive or not? 如何最有效地执行此递归/迭代CPU密集型android任务? - How can I most efficiently execute this recursive/iterative CPU intensive android task?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM