一、递归简介
- 递归是通过重复将原问题分解为同类的子问题而解决问题的方法,在编程语言中常通过函数调用自身实现。
- 以阶乘计算为例,展示了递归的计算过程,包括递推过程和回归过程,其基本思想是把规模大的问题不断分解为子问题来解决。
二、递归和数学归纳法
递归的数学模型是数学归纳法,递归终止条件对应数学归纳法第一步,递推过程对应假设部分,回归过程对应推论部分。
三、递归三步走
- 写出递推公式:找到原问题分解为子问题的规律并抽象成递推公式,对于复杂问题可假设子问题已解决,考虑如何解决原问题从而确定递推和回归过程,进而写出递推公式。
- 明确终止条件:终止条件是递归出口,通常为问题的边界值,找到后应直接给出该条件下的处理方法。
- 将递推公式和终止条件翻译成代码:
- 定义递归函数,明确函数意义、传入参数和返回结果。
- 书写递归主体,根据递推公式缩小问题规模。
- 明确递归终止条件,将终止条件和处理方法转换为代码中的条件语句和执行语句。
四、递归的注意点
- 避免栈溢出:递归利用堆栈实现,调用次数过多会导致栈空间溢出。可限制递归调用最大深度或改为非递归算法解决。
- 避免重复运算:如斐波那契数列递归中可能出现重复运算,可使用缓存保存已求解结果避免重复计算。
五、练习
1.斐波那契
- 递推公式:
- 题目中已经明确给出了斐波那契数列的递推公式为
F(n) = F(n - 1) + F(n - 2)
,其中n > 1
。
- 题目中已经明确给出了斐波那契数列的递推公式为
- 明确终止条件:
- 当
n = 0
时,F(0) = 0
。 - 当
n = 1
时,F(1) = 1
。
- 当
- 递归过程:
- 对于给定的整数
n
,如果n > 1
,则通过递归调用函数自身来计算F(n - 1)
和F(n - 2)
,然后将它们的和作为F(n)
的值返回。 - 如果
n
等于0
或1
,则直接返回对应的已知值。
- 对于给定的整数
class Solution {
public:
int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
}
};
2.爬楼梯
- 递推公式:
- 考虑最后一步到达第
n
阶楼梯的情况。如果最后一步是从n - 1
阶台阶跨一步上来的,那么有f(n - 1)
种方法到达n - 1
阶,也就对应f(n - 1)
种到达n
阶的方法;如果最后一步是从n - 2
阶台阶跨两步上来的,那么有f(n - 2)
种方法到达n - 2
阶,也就对应f(n - 2)
种到达n
阶的方法。所以总的到达n
阶的方法数f(n) = f(n - 1) + f(n - 2)
。
- 考虑最后一步到达第
- 明确终止条件:
- 当
n = 1
时,只有一种方法,即直接跨一步到达;当n = 2
时,有两种方法,可以一次跨两阶或者分两次每次跨一阶。class Solution { public: int climbStairs(int n) { if (n == 1) return 1; if (n == 2) return 2; return climbStairs(n - 1) + climbStairs(n - 2); } };
3.翻转二叉树
- 递归思路:
- 对于给定的二叉树,要翻转它,就是将每个节点的左右子树进行交换。
- 可以采用递归的方式,先递归地翻转左子树和右子树,然后再交换当前节点的左右子树指针。
- 终止条件当节点为空时,直接返回空指针,这是递归的终止条件
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) return nullptr;
TreeNode* leftInverted = invertTree(root->left);
TreeNode* rightInverted = invertTree(root->right);
root->left = rightInverted;
root->right = leftInverted;
return root;
}
};
4.发转链表
- 递归思路:
- 反转单链表可以通过递归的方式实现。假设已经反转了链表中从当前节点的下一个节点开始的部分,那么只需要将当前节点插入到反转后的链表的头部即可。
- 对于链表中的每个节点,都将其下一个节点作为新的递归输入,递归地反转后续链表,然后将当前节点插入到反转后的链表头部。
- 终止条件:
- 当链表为空或者只有一个节点时,不需要反转,直接返回该节点。
-
class Solution { public: ListNode* reverseList(ListNode* head) { if (head == nullptr || head->next == nullptr) return head; ListNode* newHead = reverseList(head->next); head->next->next = head; head->next = nullptr; return newHead; } };
5.第k个语法符号
一、递推公式
-
- 当
k
小于当前行的中间位置(即k < mid
)时,第n
行的前半部分字符与第n - 1
行的对应位置字符相同,所以可以直接递归地在第n - 1
行中查找第k
个字符,即findKthBit(n - 1, k)
。 - 当
k
大于当前行的中间位置时,由于对称性,第n
行的后半部分字符是对第n - 1
行对应位置字符取反后得到的。所以先在第n - 1
行中找到对称位置的字符(即findKthBit(n - 1, mid * 2 - k)
),然后如果这个字符是0
,则当前位置字符为1
;如果这个字符是1
,则当前位置字符为0
。
- 当
二、终止条件
- 当
n
为 1 时,无论k
是多少,第一行只有一个字符0
,所以直接返回'0'
。这是递归的终止条件。
class Solution {
public:
char findKthBit(int n, int k) {
if (n == 1) return '0';
int mid = 1 << (n - 1);
if (k == mid) return '1';
char prevChar = findKthBit(n - 1, k < mid? k : mid * 2 - k);
return k > mid? (prevChar == '0'? '1' : '0') : prevChar;
}
};