cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head==nullptr || head->next==nullptr){
return head;
}
ListNode *one = head;
ListNode *two = one->next;
ListNode *three = two->next;
two->next = one;
one->next = swapPairs(three);
return two;
}
};
可以把这种递归总结成一种很常见的 "处理当前部分 + 递归处理剩余部分 + 连接结果" 的模式。
对于两两交换链表这题,递归思路可以概括为:
1. 先判断是否还能继续递归
2. 处理当前这一小段
3. 把剩下的部分交给递归
4. 把递归结果接回来
5. 返回当前处理后的新头节点
对应 1 -> 2 -> 3 -> 4:
当前层只负责:
1 -> 2
交换成:
2 -> 1
剩余:
3 -> 4
交给递归得到:
4 -> 3
最后连接:
2 -> 1 -> 4 -> 3
所以这类链表递归最重要的思想就是:
当前递归层只解决一个小问题,剩下的问题相信递归函数能够正确解决。
一、通用递归模板
最基础的递归结构可以记成:
返回类型 function(参数) {
// 1. 递归终止条件
if (终止条件) {
return 结果;
}
// 2. 处理当前层
...
// 3. 递归处理剩余问题
auto result = function(更小的问题);
// 4. 使用递归结果
...
// 5. 返回当前层结果
return ...;
}
你做递归题的时候,可以固定问自己三个问题:
① 这个函数是干什么的?
② 什么时候停止?
③ 当前这一层需要做什么?
这是最重要的递归三问。
二、链表递归常用模板
链表题尤其适合这种形式:
ListNode* function(ListNode* head) {
// 1. 终止条件
if (head == nullptr) {
return head;
}
// 2. 保存需要的信息
ListNode* next = head->next;
// 3. 递归处理后面的链表
ListNode* newHead = function(next);
// 4. 当前节点和递归结果进行连接
...
// 5. 返回新的头节点
return ...;
}
核心可以压缩成:
处理当前节点
↓
递归处理后面的节点
↓
连接递归结果
↓
返回
三、两两交换的专用模板
这道题可以直接记:
ListNode* swapPairs(ListNode* head) {
// 少于两个节点,不需要交换
if (head == nullptr || head->next == nullptr) {
return head;
}
// 当前两个节点
ListNode* one = head;
ListNode* two = head->next;
// 后面的链表
ListNode* three = two->next;
// 当前两个交换
two->next = one;
// 剩下的交给递归
one->next = swapPairs(three);
// two 是交换后的新头节点
return two;
}
你可以进一步把它记成只有四句话:
找到前两个
↓
前两个交换
↓
剩余部分递归
↓
接回来并返回新头
也就是:
two->next = one;
one->next = swapPairs(three);
return two;
这三行实际上就是整道题的灵魂。
四、以后判断能不能用这种递归
如果一个问题满足:
当前问题
=
当前这一小部分的处理
+
剩下的同类型问题
通常就很适合递归。
比如链表:
整个链表
=
当前节点
+
后面的链表
两两交换:
整个交换问题
=
交换当前两个
+
交换剩余链表
反转链表:
整个链表反转
=
反转后面的链表
+
处理当前节点
所以你以后看到链表递归,可以优先套这个思维模板:
ListNode* dfs(ListNode* head) {
// 1. 出口
if (...) return ...;
// 2. 当前层需要的节点
...
// 3. 递归:相信它能解决后面的部分
ListNode* rest = dfs(...);
// 4. 当前层处理 / 连接
...
// 5. 返回当前问题的答案
return ...;
}
一句话记忆:
递归不是自己解决整个问题,而是"我解决当前这一层,剩下的交给下一层"。