算法笔记系列|递归

235 阅读3分钟

递归理解

递归本质上就是“递”和“归”两个过程

“递”:找到终止条件

“归”:按照给定的表达式返回到上一层

我们不需要知道机器具体如何操作,只需要我们要解决的问题是否能用递归及如何递归即可

递归需要满足的三个条件

  1. 可以拆解成多个子问题
  2. 拆解的子问题求解思路一致
  3. 存在递归终止条件

如何编写递归

  1. 写出递推公式
  2. 找到终止条件(初始值)

编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

递归代码要警惕堆栈溢出

如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险

解决:递归调用超过一定深度,我们就不继续往下再递归了,直接返回报错。

递归代码要警惕重复计算

解决:通过一个数据结构(比如散列表)来保存已经求解过的 f(k)

将递归代码改写为非递归代码

递归优缺点

  • 好处:看起来简洁易理解
  • 坏处:空间复杂度高,堆栈溢出风险

改为非递归的方法

递归是从上向下挖到最下面,然后逆向一直往上回去。 迭代,其实就是从最下面开始,然后一直往上。本质是一样的。 迭代都可以把递归改为非递归但没有解决递归的缺点,徒增了实现的复杂度。

leetcode题目

[70. 爬楼梯]

题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

思路一

  1. 判断是否可用递归
    • 可以拆解成多个子问题✅ — n可拆成多个分别求有几种方法
    • 拆解的子问题求解思路一致✅ — 每次加一或二
    • 存在递归终止条件✅ — n=1或n=2
  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. 反转链表]

题目

反转一个单链表。

思路

  1. 判断是否可用递归
    • 可以拆解成多个子问题✅ — 每个节点的next指向它的前一个节点
    • 拆解的子问题求解思路一致✅
    • 存在递归终止条件✅ — 节点的next为空
  2. 使用递归
    • 写出递推公式 — 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;
}