![](/img/trans.png)
[英]Difference between recursive task and recursive action in ForkJoinPool
[英]Tough recursive task
作为测试准备的一部分,我一直在努力解决问题,我想我可以使用你的帮助。 我需要编写一个布尔方法,它接受带有整数的数组(正数和负数),如果数组可以拆分为两个等于组,则返回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,因为数组长度不均匀。
该方法必须是递归的,允许重载,每个辅助方法也必须递归,我不需要担心复杂性。
我一直试图解决这个问题三个小时,我甚至都没有显示代码,因为我所做的所有事情都远非解决方案。
如果有人至少可以给我一些伪代码,那就太棒了。
非常感谢你!
描述的问题是分区问题的一个版本。 首先请注意,您的公式相当于决定是否存在输入的子集,其总和高达所有元素总和的一半(需要为整数,否则实例无法求解,但这很容易校验)。 基本上,在每个递归步骤中,要确定是否要将第一个数字选择到子集中,从而导致不同的递归调用。 如果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)
产生所需的值。
您要求使用伪代码,但有时将其编写为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
来说显然相当慢,但是对于你给出的问题的大小来说这很好。
您的策略应该是尝试所有可能的组合。 我将尝试记录如何实现这一目标。
注意我认为要求:让每个函数都使用递归有点困难,因为我会通过省略一些帮助函数来解决这个问题,这些函数使得代码更具可读性,所以在这种情况下我不会这样做。
通过递归,您总是希望向最终解决方案发展,并检测您何时完成。 所以我们在函数中需要两个部分:
我们的第一步中的一个技巧是,在获取集合的第一个元素之后,如果我们尝试对余数进行分区,我们不希望这两个集合相等,因为我们已经将第一个元素分配给其中一个集合。
这导致了遵循此策略的解决方案:
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之间的大小差异,减少(但您可以传递两个集合)。
另请注意,要使此实现起作用,您需要复制输入集而不是修改它!
对于一些背景信息:这个问题被称为分区问题 ,它以NP完全着称(这意味着它可能无法有效地解决大量输入数据,但很容易验证分区确实是一个解决方案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.