算法-递归
最后更新时间:
迭代转递归
我们先看个例子
1 |
|
尾递归
尾递归和一般的递归不同在对内存的占用,普通递归创建stack累积而后计算收缩,尾递归只会占用恒量的内存(和迭代一样)。尾递归是把变化的参数传递给递归函数的变量了。
怎么写尾递归?形式上只要最后一个return语句是单纯函数就可以。如:return dfs(arr, i+1);
简单来讲,尾递归是指在一个方法内部,递归调用后直接return,没有任何多余的指令了。
1 |
|
请问这个是尾递归么?答案是否定的。可能有的人会说,明明最后一个步骤就是调用dfs,为啥不是尾递归?实际上,你看到的最后一个步骤不代表从指令层面来讲的最后一步。这个方法的return先拿到dfs(n-1)的值,然后再将n与其相加,所以求dfs(n-1)并不是最后一步,因为最后还有一个add操作。
采用尾递归优化一下:
1 |
|
尾递归由于将外层方法的结果传递给了内层方法,那外层方法实际上没有任何利用价值了,直接从栈里踢出去就行了,所以可以保证同时只有一个栈帧在栈里存活,节省了大量栈空间。
递归的实现
递归和循环是等价的
如果定义一个概念需要用到这个概念本身,我们称它的定义是递归的(Recursive)
递归和循环是等价的,用循环能做的事用递归都能做,反之亦然。
尾调用 (tail call) 和尾递归 (tail recursion)
- 非尾递归算法,需要
O(n)
的调用栈空间 - 尾递归是尾调用的特殊情形,尾调用并不要求 callee 和 caller 是同一个函数。
- 尾递归太特殊了,太容易优化了,所以在源码层面就能做递归到迭代的变换。
任何的递归,都可以转换成尾调用,然后优化。
- 如果算法本身就是尾递归的,那么,可以直接改写成迭代,这是尾调用优化的一种特例。
- 如果算法本身是非尾递归的,那么,CPS 变换可以将算法改写成尾调用形式,从而可以进行尾调用优化。改写过后的空间复杂度仍然是
O(n)
,只不过是从O(n)
的栈变成了O(n)
的 continuation chain,这个改变对支持尾调用优化的简单解释器是有意义的。
剪枝
从一个集合中选择符合条件的真子集的递归二叉树DFS剪枝模板
先看题目:
18. 四数之和 给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。注意:答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
通过次数127,257提交次数325,331
*
链接:https://leetcode-cn.com/problems/4sum/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这道题不考虑时间复杂度的迭代解法
1 |
|
1 |
|
根据上述代码可以总结出模板,这里的元素类型用int/Integer为例:
1 |
|