leetcode 24两两交换链表中的节点

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 ...;
}

一句话记忆:

递归不是自己解决整个问题,而是"我解决当前这一层,剩下的交给下一层"。

相关推荐
Rain的Java大神之路9 小时前
LeetCode 37 解数独面试攻略:回溯 + 位掩码 + MRV,从暴力到毫秒级求解
java·后端·leetcode
NEFU_Jyj10 小时前
贝尔数(组合数学)
算法
希和202710 小时前
C语言程序设计题|有序数组插入数字冒泡排序
c语言·数据结构·算法
Pniubi10 小时前
力扣73 矩阵置零
算法·leetcode·矩阵
布莱克60521 小时前
C++ 中 map 的 insert 与 emplace:key 已存在时会覆盖吗?
开发语言·数据结构·c++·算法·map
D e re k ZH1 天前
C++ 系统学习手记:从语法细节到工程实践
开发语言·数据结构·c++·算法
石小石Orz1 天前
AI 正在蚕食我们的编码能力
前端·后端·算法
民乐团扒谱机1 天前
【微实验】NSGA-II(非支配排序遗传算法)——解决多目标优化问题的“自然选择模拟器”
linux·人工智能·算法
青少儿编程课堂1 天前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:中缀表达式求值解析
c++·python·算法·bfs·信息学竞赛