我很确定我完全理解只有一次递归的方法是如何工作的。
例)计算因子
public int factorial(int n){ //factorial recursion
if(n==0){
return 1;
}
else{
return n*factorial(n-1);
}
}
对于这些方法,我甚至可以想象堆栈中发生的事情以及每个堆栈级别返回的值。
但每当我遇到双递归的方法时,噩梦就开始了。
以下是编码bat的双递归的递归问题。
Ex)给定一系列整数,是否可以选择一组整数,以便该组与给定目标相加?如果是的话,是的。如果不是,那就错了。 你使用3个参数;起始索引开始,一个int数组 nums ,目标int值目标。
以下是此问题的解决方案。
public boolean groupSum(int start, int[] nums, int target) {
if (start >= nums.length) return (target == 0);
if (groupSum(start + 1, nums, target - nums[start])) return true;
if (groupSum(start + 1, nums, target)) return true;
return false;
}
我理解这个解决方案就是这个。假设我得到一个数组{2,4,8},其起始索引= 0,目标值为10.所以(0,{2,4,8},10)通过该方法进入,该函数在
重新调用if (groupSum(start + 1, nums, target - nums[start])) return true;
所以它变成(1,{2,4,8},8)
并且它一遍又一遍地直到开始索引命中
3 即可。当它达到3.最后一级(?)的堆栈进入第二次递归调用。而这正是我开始忘记发生的事情的地方。
有人可以为我打破这个吗?当人们使用双递归时,(我知道它效率很低,而且在实践中,几乎没有人用它来效率低下。但只是为了理解它。)他们真的可以想象出去了什么即将发生?或者他们只是使用它希望基本案例和递归能够正常工作?我认为这一般适用于所有编写合并排序,河内算法塔等的人。
非常感谢任何帮助..
答案 0 :(得分:2)
双递归的想法是将问题分解为两个较小的问题。一旦你解决了较小的问题,你可以加入他们的解决方案(就像在合并排序中一样)或者选择其中一个 - 这在你的例子中完成,如果解决第一个小问题,只需要解决第二个小问题没有解决完整的问题。
您的示例尝试确定是否存在输入nums
数组的子集,其总和为target
总和。 start
确定当前递归调用是否考虑了数组的哪一部分(当它为0时,将考虑整个数组)。
问题被分解为两个,因为如果存在这样的子集,它或者包含数组的第一个元素(在这种情况下,问题被简化为查找是否存在最后n-1个元素的子集) sum为target
减去第一个元素的值的数组)或不包含它的数组(在这种情况下,问题减少到查找是否有数组的最后n-1个元素的子集其总和为target
)。
第一个递归处理子集包含第一个元素的情况,这就是为什么它进行递归调用,寻找目标和减去数组剩余n-1个元素中的第一个元素。如果第一个递归返回true,则表示存在所需的子集,因此永远不会调用第二个递归。
第二次递归处理子集不包含第一个元素的情况,这就是为什么它进行递归调用以寻找数组剩余n-1个元素中的目标和的原因(这次是第一次)元素不会从目标总和中减去,因为第一个元素不包含在总和中)。同样,如果第二个递归调用返回true,则表示存在所需的子集。
答案 1 :(得分:1)
尝试这样理解:递归可以重写为while循环。其中while的条件是否定递归的停止条件。
正如已经说过的,没有什么叫做双递归。
答案 2 :(得分:1)
如果你想要想象它,通常它就像一棵树。您首先沿着树中的一条路径直到结束,然后返回第一步并选择不同的路径(如果可能)。如果没有,或者您对结果感到满意,那么您只需再退一步,等等。
我不知道这对你是否有帮助,但是当我学习递归时,它只是把我的方法看作已经有效了。 所以我想:很好,所以基本上我的方法已经工作了,但我不能用相同的参数调用它,并且必须确保通过使用不同的参数为这些确切的参数返回正确的值。
如果我们采用这个例子: 首先我们知道如果我们没有数字可以看左边,那么答案取决于目标是否为0.(第一行)
现在我们如何处理剩下的事情?嗯......我们需要考虑一下。 想想第一个数字。在什么情况下它是解决方案的一部分?好吧,如果您可以使用其余数字创建target-firstnumber。因为当你添加firstnumber时,你达到了目标。 所以你试着看看是否可能。如果是这样,它是可以解决的。 (第二行)
但如果没有,第一个数字仍然可能对解决方案不重要。因此,您必须再次尝试构建没有该数字的目标。 (第三行)
基本上就是这一切。
当然这样想你需要两件事: 1.您需要相信您的方法已经适用于其他参数 2.您需要确保递归终止。这是本例中的第一行,但你应该总是考虑是否有任何参数组合只能创建无限递归。