递归理解
递归本质上就是“递”和“归”两个过程
“递”:找到终止条件
“归”:按照给定的表达式返回到上一层
我们不需要知道机器具体如何操作,只需要我们要解决的问题是否能用递归及如何递归即可
递归需要满足的三个条件
- 可以拆解成多个子问题
- 拆解的子问题求解思路一致
- 存在递归终止条件
如何编写递归
- 写出递推公式
- 找到终止条件(初始值)
编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。
递归代码要警惕堆栈溢出
如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险
解决:递归调用超过一定深度,我们就不继续往下再递归了,直接返回报错。
递归代码要警惕重复计算
解决:通过一个数据结构(比如散列表)来保存已经求解过的 f(k)
将递归代码改写为非递归代码
递归优缺点
- 好处:看起来简洁易理解
- 坏处:空间复杂度高,堆栈溢出风险
改为非递归的方法
递归是从上向下挖到最下面,然后逆向一直往上回去。 迭代,其实就是从最下面开始,然后一直往上。本质是一样的。 迭代都可以把递归改为非递归但没有解决递归的缺点,徒增了实现的复杂度。
leetcode题目
[70. 爬楼梯]
题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
思路一
- 判断是否可用递归
- 可以拆解成多个子问题✅ — n可拆成多个分别求有几种方法
- 拆解的子问题求解思路一致✅ — 每次加一或二
- 存在递归终止条件✅ — n=1或n=2
- 使用递归
-
写出递推公式
f(n) = f(n-1) + f(n-2)
-
找终止条件 — n=1或n=2
-
代码一
int climbStairs(int n){
int sum = 0;
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 2;
sum = climbStairs(n-1) + climbStairs(n-2);
return sum;
}
但是!!!这样做在提交leetcode的时候n=44时就报超时了!
[206. 反转链表]
题目
反转一个单链表。
思路
- 判断是否可用递归
- 可以拆解成多个子问题✅ — 每个节点的next指向它的前一个节点
- 拆解的子问题求解思路一致✅
- 存在递归终止条件✅ — 节点的next为空
- 使用递归
- 写出递推公式 — head->next->next = head;
- 找终止条件 — 节点的next为空
代码
struct ListNode* reverseList(struct ListNode* head){
if(head == NULL || head->next == NULL) return head;
struct ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = NULL;// 避免形成环
return newHead;
}