LeetCode 热题 100-----23.反转链表

一、题目重述

给你一个单链表的头节点 head,要求你反转这个链表(不交换节点里的值,只反转节点之间的连接关系),最后返回反转后的链表的头节点。

举个直白的例子:

示例1:输入的链表是 1 → 2 → 3 → 4 → 5 → 空,反转后变成 5 → 4 → 3 → 2 → 1 → 空,返回的头节点就是5;

示例2:输入的链表是 1 → 2 → 空,反转后变成 2 → 1 → 空,返回的头节点是2;

示例3:输入的是空链表(没有任何节点),反转后还是空链表,返回空。

提示:链表节点个数在 0 到 5000 之间,节点的值在 -5000 到 5000 之间,不用考虑极端数据的异常处理。

二、前置知识

在做这道题之前,你必须先搞懂 3 个核心知识点,否则看代码会完全懵------我会用"大白话+生活例子"讲透,不搞专业术语堆砌。

2.1 什么是单链表?(核心中的核心)

单链表是一种"链式存储"的数据结构,不像数组那样是连续的内存空间,而是由一个个"节点"通过"连接关系"串起来的,就像一串糖葫芦 或者 一列火车车厢

  • 每一颗山楂 = 一个「节点」(ListNode);

  • 山楂上的数字 = 节点的值(val),比如山楂上的1、2、3,就是节点的val=1、val=2;

  • 连接山楂的竹签 = 节点的"指针"(C++里叫指针,Python里叫引用,本质一样),也就是节点里的next属性,作用是"指向当前节点的下一个节点";

  • 最后一颗山楂没有竹签 = 最后一个节点的next指向"空"(C++里写nullptr,Python里写None),表示链表到此结束。

补充:链表的"头节点"(head)就是链表的"第一个节点",相当于糖葫芦的"第一个山楂",我们操作链表,都是从 head 开始的。

2.2 节点的结构(题目已经定义好,我们直接用,但要懂它的意思)

不管是C++还是Python,题目都已经帮我们定义好了"节点"的样子,我们不需要自己定义,但必须看懂每个部分的作用:

2.2.1 C++的节点结构(struct结构体)

cpp 复制代码
// 这是题目自带的,不用我们写,看懂即可
struct ListNode {
    int val;          // 节点的值(比如1、2、3)
    ListNode *next;   // 指针:指向当前节点的下一个节点(存的是下一个节点的地址)
    // 下面是3个构造函数(用来创建节点的"工具"),不用记,知道怎么用就行
    ListNode() : val(0), next(nullptr) {}  // 无参构造:创建一个val=0、next为空的节点
    ListNode(int x) : val(x), next(nullptr) {}  // 单参构造:创建一个val=x、next为空的节点
    ListNode(int x, ListNode *next) : val(x), next(next) {}  // 双参构造:创建val=x、next指向指定节点的节点
};

大白话解释:比如我们想创建一个"val=5、next为空"的节点,就可以写 ListNode* node = new ListNode(5);;如果想创建"val=4、next指向刚才的node节点",就写 ListNode* node4 = new ListNode(4, node);,这样就形成了 4 → 5 → 空 的小链表。

2.2.2 Python的节点结构(class类)

python 复制代码
# 这是题目自带的,不用我们写,看懂即可
class ListNode:
    def __init__(self, val=0, next=None):  # 初始化方法(创建节点时自动执行)
        self.val = val  # 节点的值
        self.next = next  # 引用:指向当前节点的下一个节点(存的是下一个节点的对象)

大白话解释:Python里创建节点更简单,比如创建val=5、next为空的节点,写 node = ListNode(5);创建val=4、next指向node的节点,写 node4 = ListNode(4, node),和C++逻辑完全一样。

2.3 什么是"指针/引用"?

不用记复杂定义,记住一句话:指针/引用就是"地址",用来找到下一个节点

比如C++里的 ListNode* next,next里存的是"下一个节点在内存中的地址",我们通过这个地址,就能找到下一个节点;Python里的 self.next,存的是"下一个节点的对象",本质也是通过这个对象,找到下一个节点。

举个生活例子:你想找朋友A,你不知道A在哪,但你知道A的地址(比如XX小区3号楼2单元501),这个"地址"就是指针/引用;你通过地址找到A,就相当于通过next找到下一个节点。

2.4 链表的遍历

遍历就是"从头到尾,一个个看链表的每个节点",比如我们想打印链表的所有值,思路就是:

  1. 从 head(头节点)开始;

  2. 先打印当前节点的值;

  3. 通过当前节点的next,找到下一个节点;

  4. 重复步骤2-3,直到当前节点为空(表示走到链表末尾)。

后续我们写测试用例时,会用到这个遍历逻辑,用来打印输入和输出的链(再强调一次,避免走偏)

⚠️ 重点:不允许交换节点的val值,必须通过"修改节点的next指针/引用",实现链表反转。

比如链表 1→2→3,不能把val改成3、2、1,而是要把1的next改成空,2的next改成1,3的next改成2,最终变成3→2→1。

三、解法一:迭代法(首选!最容易懂、效率最高,必学)

迭代法的核心思路:用3个指针,一步步"反转"每个节点的指向,就像走路时"一步步回头",把每个节点的"前进方向"(next)反过来。

我们先搞懂3个指针的作用(记死,代码里全靠它们):

  • prev:全称previous(之前的),记录「当前节点的前一个节点」,初始值为空(因为反转后,第一个节点的next要指向空);

  • curr:全称current(当前的),记录「正在处理的节点」,初始值为head(从链表头开始处理);

  • nextTemp:全称next temporary(临时next),临时保存当前节点的下一个节点(最关键!因为我们要修改curr的next,不保存的话,会丢失后面的所有节点,相当于糖葫芦断了)。

迭代法的执行步骤(配合例子,一步一步看,绝对能懂):

以示例1(输入1→2→3→4→5→空)为例:

  1. 初始状态:prev = 空,curr = 1(head),nextTemp = 空;

  2. 第一步:保存curr的下一个节点(nextTemp = curr.next = 2),防止断链;

  3. 第二步:反转curr的指向(curr.next = prev = 空),此时1的next指向空;

  4. 第三步:移动指针(prev = curr = 1,curr = nextTemp = 2),准备处理下一个节点;

  5. 重复步骤2-4,直到curr为空(处理完所有节点);

  6. 最终,prev就是反转后的新头节点(5),因为循环结束时,curr为空,prev指向最后一个处理的节点(5)。

3.1 C++ 迭代法完整代码(每句带注释,可直接复制运行)

包含:解题函数 + 辅助函数(创建链表、打印链表,用于测试) + main主函数(测试题目3个案例+2个自定义案例)

cpp 复制代码
#include <iostream>
using namespace std;

// 题目自带的节点结构,不用修改
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) {}
};

// 辅助函数1:根据数组创建链表(比如输入[1,2,3],创建1→2→3→空的链表,返回头节点)
// 不用纠结这个函数的实现,会用就行,后面测试会调用
ListNode* createList(int arr[], int n) {
    if (n == 0) return nullptr;  // 数组为空,返回空链表
    // 创建头节点(第一个节点),val为数组第一个元素
    ListNode* head = new ListNode(arr[0]);
    ListNode* curr = head;  // 临时指针,用来串联后续节点
    for (int i = 1; i < n; i++) {
        // 逐个创建节点,连接到当前链表的末尾
        curr->next = new ListNode(arr[i]);
        curr = curr->next;  // 指针后移,指向新的末尾节点
    }
    return head;  // 返回创建好的链表的头节点
}

// 辅助函数2:打印链表(遍历链表,输出所有节点的值,方便我们查看结果)
void printList(ListNode* head) {
    ListNode* curr = head;  // 从 head 开始遍历
    while (curr != nullptr) {  // 只要当前节点不为空,就继续遍历
        cout << curr->val << " → ";  // 打印当前节点的值
        curr = curr->next;  // 指向 next 节点,继续遍历
    }
    cout << "null" << endl;  // 遍历结束,打印null,表示链表末尾
}

// 核心解题函数:反转链表(题目要求实现的函数)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 1. 定义3个核心指针,初始化
        ListNode* prev = nullptr;  // prev初始为空(第一个节点反转后指向空)
        ListNode* curr = head;     // curr初始为头节点(从第一个节点开始处理)
        ListNode* nextTemp = nullptr;  // 临时保存next,初始为空

        // 2. 循环遍历链表,直到curr为空(处理完所有节点)
        // 为什么循环条件是curr != nullptr?因为curr是"当前要处理的节点",只要它不为空,就需要处理
        while (curr != nullptr) {
            // 步骤1:保存curr的下一个节点(关键!不保存的话,修改curr->next后会丢后面的节点)
            nextTemp = curr->next;
            // 步骤2:反转curr的指向------让curr的next指向prev(原来的前一个节点)
            curr->next = prev;
            // 步骤3:移动prev和curr指针,准备处理下一个节点
            prev = curr;  // prev往前走一步,走到当前curr的位置
            curr = nextTemp;  // curr往前走一步,走到之前保存的nextTemp(下一个节点)
        }

        // 3. 循环结束后,curr为空,prev指向最后一个节点(就是反转后的新头节点)
        // 为什么返回prev?因为此时prev是最后一个处理的节点,也是反转后链表的第一个节点
        return prev;
    }
};

// 主函数:测试用例(题目案例+自定义案例,可以直接运行看结果)
int main() {
    Solution sol;  // 创建解题类的对象,用于调用reverseList函数

    // 测试案例1:输入[1,2,3,4,5](题目示例1)
    cout << "测试案例1:" << endl;
    int arr1[] = {1,2,3,4,5};
    int n1 = sizeof(arr1)/sizeof(arr1[0]);  // 计算数组长度(5个元素)
    ListNode* head1 = createList(arr1, n1);  // 创建链表
    cout << "反转前链表:";
    printList(head1);  // 打印反转前的链表
    ListNode* res1 = sol.reverseList(head1);  // 调用反转函数
    cout << "反转后链表:";
    printList(res1);  // 打印反转后的链表
    cout << "------------------------" << endl;

    // 测试案例2:输入[1,2](题目示例2)
    cout << "测试案例2:" << endl;
    int arr2[] = {1,2};
    int n2 = sizeof(arr2)/sizeof(arr2[0]);
    ListNode* head2 = createList(arr2, n2);
    cout << "反转前链表:";
    printList(head2);
    ListNode* res2 = sol.reverseList(head2);
    cout << "反转后链表:";
    printList(res2);
    cout << "------------------------" << endl;

    // 测试案例3:输入空链表(题目示例3)
    cout << "测试案例3:" << endl;
    int arr3[] = {};
    int n3 = 0;
    ListNode* head3 = createList(arr3, n3);
    cout << "反转前链表:";
    printList(head3);
    ListNode* res3 = sol.reverseList(head3);
    cout << "反转后链表:";
    printList(res3);
    cout << "------------------------" << endl;

    // 自定义测试案例4:输入单个节点[6](边界情况)
    cout << "自定义测试案例4(单个节点):" << endl;
    int arr4[] = {6};
    int n4 = sizeof(arr4)/sizeof(arr4[0]);
    ListNode* head4 = createList(arr4, n4);
    cout << "反转前链表:";
    printList(head4);
    ListNode* res4 = sol.reverseList(head4);
    cout << "反转后链表:";
    printList(res4);
    cout << "------------------------" << endl;

    // 自定义测试案例5:输入[3,7,9,12](多节点,验证通用性)
    cout << "自定义测试案例5(多节点):" << endl;
    int arr5[] = {3,7,9,12};
    int n5 = sizeof(arr5)/sizeof(arr5[0]);
    ListNode* head5 = createList(arr5, n5);
    cout << "反转前链表:";
    printList(head5);
    ListNode* res5 = sol.reverseList(head5);
    cout << "反转后链表:";
    printList(res5);

    return 0;
}

3.2 C++ 迭代法 运行过程拆解(逐步看,彻底懂)

以测试案例1(输入1→2→3→4→5→空)为例,逐次循环拆解3个指针的变化:

  1. 初始状态:prev = nullptr,curr = 1,nextTemp = nullptr;

  2. 第一次循环(curr=1):

    1. nextTemp = curr->next = 2;

    2. curr->next = prev = nullptr(此时1→空);

    3. prev = curr = 1,curr = nextTemp = 2;

    4. 当前链表状态:1→空,2→3→4→5→空(prev指向1,curr指向2)。

  3. 第二次循环(curr=2):

    1. nextTemp = curr->next = 3;

    2. curr->next = prev = 1(此时2→1→空);

    3. prev = curr = 2,curr = nextTemp = 3;

    4. 当前链表状态:2→1→空,3→4→5→空(prev指向2,curr指向3)。

  4. 第三次循环(curr=3):

    1. nextTemp = 4;

    2. curr->next = 2(3→2→1→空);

    3. prev=3,curr=4;

  5. 第四次循环(curr=4):

    1. nextTemp = 5;

    2. curr->next = 3(4→3→2→1→空);

    3. prev=4,curr=5;

  6. 第五次循环(curr=5):

    1. nextTemp = nullptr(5是最后一个节点,next为空);

    2. curr->next = 4(5→4→3→2→1→空);

    3. prev=5,curr=nextTemp = nullptr;

  7. 循环结束(curr为空),返回prev=5,就是反转后的头节点,最终链表为5→4→3→2→1→空,和题目示例一致。

3.3 Python 迭代法完整代码(每句带注释,可直接复制运行)

包含:解题函数 + 辅助函数(创建链表、打印链表) + 测试代码(题目案例+自定义案例)

python 复制代码
from typing import Optional  # 导入类型提示(必须加,否则Optional会报错,不用懂,加上就行)

# 题目自带的节点类,不用修改
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# 辅助函数1:根据列表创建链表(比如输入[1,2,3],创建1→2→3→None的链表,返回头节点)
def create_list(arr: list) -> Optional[ListNode]:
    if not arr:  # 如果列表为空,返回空链表
        return None
    # 创建头节点,val为列表第一个元素
    head = ListNode(arr[0])
    curr = head  # 临时指针,用来串联后续节点
    for val in arr[1:]:  # 从列表第二个元素开始,逐个创建节点
        curr.next = ListNode(val)  # 创建新节点,连接到当前链表末尾
        curr = curr.next  # 指针后移,指向新的末尾节点
    return head  # 返回创建好的链表头节点

# 辅助函数2:打印链表(遍历链表,输出所有节点的值)
def print_list(head: Optional[ListNode]) -> None:
    curr = head  # 从head开始遍历
    while curr:  # 只要当前节点不为None,就继续遍历(Python里None相当于C++的nullptr)
        print(curr.val, end=" → ")  # 打印当前节点的值,不换行
        curr = curr.next  # 指向next节点,继续遍历
    print("None")  # 遍历结束,打印None,表示链表末尾

# 核心解题类(题目要求实现的类和方法)
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 1. 定义3个核心指针,初始化(和C++逻辑完全一样)
        prev = None  # prev初始为空,第一个节点反转后指向None
        curr = head  # curr初始为头节点,从第一个节点开始处理
        next_temp = None  # 临时保存next,初始为空

        # 2. 循环遍历链表,直到curr为None(处理完所有节点)
        while curr:  # 等价于 while curr is not None
            # 步骤1:保存curr的下一个节点(关键!防止修改curr.next后丢失后面的节点)
            next_temp = curr.next
            # 步骤2:反转curr的指向------让curr的next指向prev(原来的前一个节点)
            curr.next = prev
            # 步骤3:移动prev和curr指针,准备处理下一个节点
            prev = curr  # prev往前走一步,走到当前curr的位置
            curr = next_temp  # curr往前走一步,走到之前保存的next_temp

        # 3. 循环结束后,curr为None,prev就是反转后的新头节点
        return prev

# 测试代码(可以直接运行,查看所有案例的结果)
if __name__ == "__main__":
    sol = Solution()  # 创建解题类的对象

    # 测试案例1:输入[1,2,3,4,5](题目示例1)
    print("测试案例1:")
    arr1 = [1,2,3,4,5]
    head1 = create_list(arr1)  # 创建链表
    print("反转前链表:", end="")
    print_list(head1)  # 打印反转前
    res1 = sol.reverseList(head1)  # 调用反转函数
    print("反转后链表:", end="")
    print_list(res1)  # 打印反转后
    print("-" * 40)

    # 测试案例2:输入[1,2](题目示例2)
    print("测试案例2:")
    arr2 = [1,2]
    head2 = create_list(arr2)
    print("反转前链表:", end="")
    print_list(head2)
    res2 = sol.reverseList(head2)
    print("反转后链表:", end="")
    print_list(res2)
    print("-" * 40)

    # 测试案例3:输入空列表(题目示例3)
    print("测试案例3:")
    arr3 = []
    head3 = create_list(arr3)
    print("反转前链表:", end="")
    print_list(head3)
    res3 = sol.reverseList(head3)
    print("反转后链表:", end="")
    print_list(res3)
    print("-" * 40)

    # 自定义测试案例4:输入单个节点[6](边界情况)
    print("自定义测试案例4(单个节点):")
    arr4 = [6]
    head4 = create_list(arr4)
    print("反转前链表:", end="")
    print_list(head4)
    res4 = sol.reverseList(head4)
    print("反转后链表:", end="")
    print_list(res4)
    print("-" * 40)

    # 自定义测试案例5:输入[3,7,9,12](多节点,验证通用性)
    print("自定义测试案例5(多节点):")
    arr5 = [3,7,9,12]
    head5 = create_list(arr5)
    print("反转前链表:", end="")
    print_list(head5)
    res5 = sol.reverseList(head5)
    print("反转后链表:", end="")
    print_list(res5)

3.4 Python 迭代法 运行过程拆解(和C++完全一致,可对照看)

还是以测试案例1(1→2→3→4→5→None)为例,Python的指针(引用)变化和C++完全一样,只是语法不同:

  1. 初始状态:prev = None,curr = 1(head),next_temp = None;

  2. 第一次循环(curr=1):

    1. next_temp = curr.next = 2;

    2. curr.next = prev = None(1→None);

    3. prev = 1,curr = 2;

  3. 第二次循环(curr=2):

    1. next_temp = 3;

    2. curr.next = 1(2→1→None);

    3. prev=2,curr=3;

  4. 第三次循环(curr=3):next_temp=4,curr.next=2(3→2→1→None),prev=3,curr=4;

  5. 第四次循环(curr=4):next_temp=5,curr.next=3(4→3→2→1→None),prev=4,curr=5;

  6. 第五次循环(curr=5):next_temp=None,curr.next=4(5→4→3→2→1→None),prev=5,curr=None;

  7. 循环结束,返回prev=5,反转完成。

⚠️ 注意:Python里的"None"和C++里的"nullptr"完全一样,都是"空"的意思;Python的"curr is not None"和C++的"curr != nullptr"是同一个逻辑。

四、解法二:递归法(进阶!代码极简,面试加分,理解需多花点时间)

递归法的核心思路:"递到最后,再归回来"。简单说就是:先一路递归到链表的最后一个节点(这个节点就是反转后的新头节点),然后在"回溯"的过程中,逐个反转每个节点的指向。

递归的关键(记死):

  • 递(下去):一直递归,直到找到链表的最后一个节点(终止条件:当前节点为空,或当前节点是最后一个节点);

  • 归(回来):回溯时,让当前节点的"下一个节点"的next,指向当前节点,然后把当前节点的next置为空(防止链表成环);

  • 全程返回"新头节点"(也就是链表的最后一个节点,递归过程中这个节点一直不变)。

举个直白的例子(还是1→2→3→4→5→空):

  1. 递:reverseList(1) → reverseList(2) → reverseList(3) → reverseList(4) → reverseList(5);

  2. 终止:reverseList(5)时,5是最后一个节点(5.next为空),返回5(新头节点);

  3. 归:

    1. 回到reverseList(4):此时当前节点是4,下一个节点是5(刚才返回的新头),让5.next = 4,4.next = None(此时4→5→None),返回5;

    2. 回到reverseList(3):当前节点是3,下一个节点是4,让4.next = 3,3.next = None(3→4→5→None),返回5;

    3. 回到reverseList(2):当前节点是2,下一个节点是3,让3.next = 2,2.next = None(2→3→4→5→None),返回5;

    4. 回到reverseList(1):当前节点是1,下一个节点是2,让2.next = 1,1.next = None(1→2→3→4→5→None 反转成 5→4→3→2→1→None),返回5;

  4. 最终返回5,就是反转后的新头节点。

4.1 C++ 递归法完整代码(每句带注释,可直接复制运行)

包含:解题函数 + 辅助函数(和迭代法一样,用于创建、打印链表) + main主函数(测试所有案例)

cpp 复制代码
#include <iostream>
using namespace std;

// 题目自带的节点结构
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) {}
};

// 辅助函数1:创建链表(和迭代法完全一样,不用改)
ListNode* createList(int arr[], int n) {
    if (n == 0) return nullptr;
    ListNode* head = new ListNode(arr[0]);
    ListNode* curr = head;
    for (int i = 1; i < n; i++) {
        curr->next = new ListNode(arr[i]);
        curr = curr->next;
    }
    return head;
}

// 辅助函数2:打印链表(和迭代法完全一样,不用改)
void printList(ListNode* head) {
    ListNode* curr = head;
    while (curr != nullptr) {
        cout << curr->val << " → ";
        curr = curr->next;
    }
    cout << "null" << endl;
}

// 核心解题函数:递归法反转链表
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 递归终止条件:当前节点为空,或当前节点是最后一个节点(next为空)
        // 为什么?因为空链表不用反转,直接返回;最后一个节点反转后还是自己,作为新头节点
        if (head == nullptr || head->next == nullptr) {
            return head;  // 返回当前节点(空,或最后一个节点)
        }

        // 1. 递:递归反转当前节点的下一个节点开始的所有链表,得到新头节点
        // 这里的newHead就是链表的最后一个节点,全程不变,一直返回
        ListNode* newHead = reverseList(head->next);

        // 2. 归:回溯时,反转当前节点和下一个节点的指向
        // 第一步:让当前节点的下一个节点(head->next)的next,指向当前节点(head)
        head->next->next = head;
        // 第二步:让当前节点的next指向空(防止链表成环,比如1→2→1)
        head->next = nullptr;

        // 3. 返回新头节点(全程都是最后一个节点,不会变)
        return newHead;
    }
};

// 主函数:测试用例(和迭代法完全一样,测试所有案例)
int main() {
    Solution sol;

    // 测试案例1:[1,2,3,4,5]
    cout << "测试案例1:" << endl;
    int arr1[] = {1,2,3,4,5};
    int n1 = sizeof(arr1)/sizeof(arr1[0]);
    ListNode* head1 = createList(arr1, n1);
    cout << "反转前链表:";
    printList(head1);
    ListNode* res1 = sol.reverseList(head1);
    cout << "反转后链表:";
    printList(res1);
    cout << "------------------------" << endl;

    // 测试案例2:[1,2]
    cout << "测试案例2:" << endl;
    int arr2[] = {1,2};
    int n2 = sizeof(arr2)/sizeof(arr2[0]);
    ListNode* head2 = createList(arr2, n2);
    cout << "反转前链表:";
    printList(head2);
    ListNode* res2 = sol.reverseList(head2);
    cout << "反转后链表:";
    printList(res2);
    cout << "------------------------" << endl;

    // 测试案例3:空链表
    cout << "测试案例3:" << endl;
    int arr3[] = {};
    int n3 = 0;
    ListNode* head3 = createList(arr3, n3);
    cout << "反转前链表:";
    printList(head3);
    ListNode* res3 = sol.reverseList(head3);
    cout << "反转后链表:";
    printList(res3);
    cout << "------------------------" << endl;

    // 自定义测试案例4:单个节点[6]
    cout << "自定义测试案例4(单个节点):" << endl;
    int arr4[] = {6};
    int n4 = sizeof(arr4)/sizeof(arr4[0]);
    ListNode* head4 = createList(arr4, n4);
    cout << "反转前链表:";
    printList(head4);
    ListNode* res4 = sol.reverseList(head4);
    cout << "反转后链表:";
    printList(res4);
    cout << "------------------------" << endl;

    // 自定义测试案例5:[3,7,9,12]
    cout << "自定义测试案例5(多节点):" << endl;
    int arr5[] = {3,7,9,12};
    int n5 = sizeof(arr5)/sizeof(arr5[0]);
    ListNode* head5 = createList(arr5, n5);
    cout << "反转前链表:";
    printList(head5);
    ListNode* res5 = sol.reverseList(head5);
    cout << "反转后链表:";
    printList(res5);

    return 0;
}

4.2 C++ 递归法 运行过程拆解(逐步看,搞懂"递"和"归")

还是以测试案例1(1→2→3→4→5→空)为例,拆解递归的每一步:

  1. 调用 reverseList(head=1):

    1. 判断:1 != nullptr,1->next=2 != nullptr → 不满足终止条件;

    2. 调用 reverseList(head=2),进入下一层递归。

  2. 调用 reverseList(head=2):

    1. 判断:2 != nullptr,2->next=3 != nullptr → 不满足终止条件;

    2. 调用 reverseList(head=3),进入下一层递归。

  3. 调用 reverseList(head=3):

    1. 判断:3 != nullptr,3->next=4 != nullptr → 不满足终止条件;

    2. 调用 reverseList(head=4),进入下一层递归。

  4. 调用 reverseList(head=4):

    1. 判断:4 != nullptr,4->next=5 != nullptr → 不满足终止条件;

    2. 调用 reverseList(head=5),进入下一层递归。

  5. 调用 reverseList(head=5):

    1. 判断:5 != nullptr,但5->next=nullptr → 满足终止条件;

    2. 返回5(newHead=5),回到上一层(reverseList(4))。

  6. 回溯到 reverseList(4):

    1. 此时 newHead=5,当前节点head=4;

    2. head->next->next = head → 4->next(5)->next = 4 → 5->next=4;

    3. head->next = nullptr → 4->next=nullptr(此时4→5→空);

    4. 返回newHead=5,回到上一层(reverseList(3))。

  7. 回溯到 reverseList(3):

    1. newHead=5,当前节点head=3;

    2. 3->next(4)->next = 3 → 4->next=3;

    3. 3->next=nullptr(此时3→4→5→空);

    4. 返回5,回到上一层(reverseList(2))。

  8. 回溯到 reverseList(2):

    1. newHead=5,当前节点head=2;

    2. 2->next(3)->next = 2 → 3->next=2;

    3. 2->next=nullptr(此时2→3→4→5→空);

    4. 返回5,回到上一层(reverseList(1))。

  9. 回溯到 reverseList(1):

    1. newHead=5,当前节点head=1;

    2. 1->next(2)->next = 1 → 2->next=1;

    3. 1->next=nullptr(此时1→2→3→4→5→空 反转成 5→4→3→2→1→空);

    4. 返回5,递归结束。

注意:递归的"递"是"下去","归"是"回来",每一层递归都会保存当前节点的状态,回溯时再处理,核心是"先找到最后一个节点(新头),再从后往前反转指向"。

4.3 Python 递归法完整代码(每句带注释,可直接复制运行)

python 复制代码
from typing import Optional

# 题目自带的节点类
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# 辅助函数1:创建链表(和迭代法一样)
def create_list(arr: list) -> Optional[ListNode]:
    if not arr:
        return None
    head = ListNode(arr[0])
    curr = head
    for val in arr[1:]:
        curr.next = ListNode(val)
        curr = curr.next
    return head

# 辅助函数2:打印链表(和迭代法一样)
def print_list(head: Optional[ListNode]) -> None:
    curr = head
    while curr:
        print(curr.val, end=" → ")
        curr = curr.next
    print("None")

# 核心解题类
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 递归终止条件:当前节点为空,或当前节点是最后一个节点(next为None)
        # 逻辑和C++完全一样:空链表直接返回,最后一个节点作为新头
        if not head or not head.next:
            return head

        # 1. 递:递归反转当前节点的下一个节点开始的链表,得到新头节点
        new_head = self.reverseList(head.next)

        # 2. 归:回溯时,反转当前节点和下一个节点的指向
        # 让下一个节点的next指向当前节点
        head.next.next = head
        # 让当前节点的next指向None,防止成环
        head.next = None

        # 3. 返回新头节点(全程不变)
        return new_head

# 测试代码(和迭代法一样,测试所有案例)
if __name__ == "__main__":
    sol = Solution()

    # 测试案例1:[1,2,3,4,5](题目示例1)
    print("测试案例1:")
    arr1 = [1,2,3,4,5]
    head1 = create_list(arr1)  # 创建链表
    print("反转前链表:", end="")
    print_list(head1)  # 打印反转前
    res1 = sol.reverseList(head1)  # 调用递归反转函数
    print("反转后链表:", end="")
    print_list(res1)  # 打印反转后
    print("-" * 40)

    # 测试案例2:[1,2](题目示例2)
    print("测试案例2:")
    arr2 = [1,2]
    head2 = create_list(arr2)
    print("反转前链表:", end="")
    print_list(head2)
    res2 = sol.reverseList(head2)
    print("反转后链表:", end="")
    print_list(res2)
    print("-" * 40)

    # 测试案例3:空列表(题目示例3)
    print("测试案例3:")
    arr3 = []
    head3 = create_list(arr3)
    print("反转前链表:", end="")
    print_list(head3)
    res3 = sol.reverseList(head3)
    print("反转后链表:", end="")
    print_list(res3)
    print("-" * 40)

    # 自定义测试案例4:单个节点[6](边界情况)
    print("自定义测试案例4(单个节点):")
    arr4 = [6]
    head4 = create_list(arr4)
    print("反转前链表:", end="")
    print_list(head4)
    res4 = sol.reverseList(head4)
    print("反转后链表:", end="")
    print_list(res4)
    print("-" * 40)

    # 自定义测试案例5:[3,7,9,12](多节点,验证通用性)
    print("自定义测试案例5(多节点):")
    arr5 = [3,7,9,12]
    head5 = create_list(arr5)
    print("反转前链表:", end="")
    print_list(head5)
    res5 = sol.reverseList(head5)
    print("反转后链表:", end="")
    print_list(res5)

4.4 Python 递归法 运行过程拆解(和C++完全一致,对照看)

还是以测试案例1(1→2→3→4→5→None)为例,Python递归的"递"和"归"过程,和C++逻辑完全一样,只是语法不同,逐步拆解如下,跟着走一遍就能懂:

  1. 调用 sol.reverseList(head=1)(测试代码中调用,进入核心函数): 判断:head=1 不为None,head.next=2 也不为None → 不满足终止条件;

  2. 调用 self.reverseList(head=2),进入下一层递归(递下去)。

  3. 调用 self.reverseList(head=2): 判断:head=2 不为None,head.next=3 不为None → 不满足终止条件;

  4. 调用 self.reverseList(head=3),进入下一层递归。

  5. 调用 self.reverseList(head=3): 判断:head=3 不为None,head.next=4 不为None → 不满足终止条件;

  6. 调用 self.reverseList(head=4),进入下一层递归。

  7. 调用 self.reverseList(head=4): 判断:head=4 不为None,head.next=5 不为None → 不满足终止条件;

  8. 调用 self.reverseList(head=5),进入下一层递归(递到最底层)。

  9. 调用 self.reverseList(head=5): 判断:head=5 不为None,但 head.next=None(5是最后一个节点) → 满足终止条件;

  10. 返回 head=5(这就是新头节点new_head),回到上一层递归(reverseList(4))。

  11. 回溯到 self.reverseList(head=4): 此时 new_head=5(上一层返回的结果),当前节点head=4;

  12. 执行 head.next.next = head → 4的next是5,所以5.next = 4(此时5→4);

  13. 执行 head.next = None → 4.next = None(此时4→5→None,避免成环);

  14. 返回 new_head=5,回到上一层递归(reverseList(3))。

  15. 回溯到 self.reverseList(head=3): new_head=5,当前节点head=3;

  16. head.next.next = head → 3的next是4,所以4.next = 3(此时4→3);

  17. head.next = None → 3.next = None(此时3→4→5→None);

  18. 返回 new_head=5,回到上一层递归(reverseList(2))。

  19. 回溯到 self.reverseList(head=2): new_head=5,当前节点head=2;

  20. head.next.next = head → 2的next是3,所以3.next = 2(此时3→2);

  21. head.next = None → 2.next = None(此时2→3→4→5→None);

  22. 返回 new_head=5,回到上一层递归(reverseList(1))。

  23. 回溯到 self.reverseList(head=1): new_head=5,当前节点head=1;

  24. head.next.next = head → 1的next是2,所以2.next = 1(此时2→1);

  25. head.next = None → 1.next = None(此时1→2→3→4→5→None 反转成 5→4→3→2→1→None);

  26. 返回 new_head=5,递归结束,函数调用完成。

注意:Python的递归调用栈和C++一样,每一层递归都会暂时"保存"当前节点的状态,等下一层递归返回后,再继续处理当前节点的指向反转,全程new_head都是5,不会变,最终返回的就是反转后的新头节点。

五、两种方法对比(必看,选择适合自己的)

前面我们讲了迭代法(易)和递归法(进阶),两种方法都能正确解决"反转链表"问题,但适用场景和特点不同,用表格清晰对比,能快速选择:

方法类型 优点 缺点 空间复杂度 时间复杂度 适用场景
迭代法 1. 逻辑直观,一步步反转,最容易理解;2. 无栈溢出风险(不依赖递归调用栈);3. 工程中最常用,效率稳定。 代码行数比递归法稍多,需要记住3个指针的作用和移动顺序。 O(1)(只用到3个指针,不额外占用空间) O(n)(只遍历一次链表,每个节点处理一次) 入门、面试首选、工程开发(推荐所有先掌握)
递归法 1. 代码极简,核心逻辑只有3行;2. 逻辑优雅,体现"递归思想",面试时能加分。 1. 理解难度高,需要掌握"递"和"归"的流程;2. 链表过长时(比如节点数5000),可能出现栈溢出(递归调用栈深度最大为n)。 O(n)(递归调用栈占用空间,深度等于链表长度) O(n)(同样只遍历一次链表,递归调用次数等于节点数) 算法进阶、面试加分、链表长度较小时使用

六、常见问题汇总(避坑指南)

我整理了学习这道题时,最容易踩的5个坑,提前规避,少走弯路:

  1. 坑1:忘记保存next节点,导致链表断链 解决:迭代法中,必须先保存curr.next(nextTemp/next_temp),再修改curr.next的指向,这一步是核心,不能颠倒顺序。

  2. 坑2:递归法忘记将当前节点的next置为None,导致链表成环 解决:回溯时,除了让head.next.next = head,必须加上head.next = None,否则会出现1→2→1的死循环(链表成环)。

  3. 坑3:混淆"空链表"和"单节点链表"的边界情况 解决:两种方法都已经处理了这两种情况(迭代法循环不执行,递归法触发终止条件),不用额外加判断,直接用代码即可。

  4. 坑4:Python中把None写成null,C++中把nullptr写成None 解决:记死:Python用None,C++用nullptr,两者不能混用,否则会报错。

  5. 坑5:试图通过交换节点val值来反转链表 解决:题目核心要求是"反转节点连接关系",不允许交换val值,即使能得到正确结果,也不符合题目要求(面试会扣分)。

七、总结(必看,快速梳理重点)

  1. 前置知识:先搞懂「单链表结构」「节点的val和next属性」「指针/引用的含义」,这是看懂代码的基础,没懂的话回头再看第二部分;

  2. 解法选择:优先学「迭代法」,3个指针(prev、curr、nextTemp)一步步反转,记准"保存→反转→移动"的步骤,就能掌握;

  3. 递归法:进阶学习,核心是"递到最后,归回来反转",记住终止条件和回溯时的两步操作(反转指向+置空next);

  4. 测试代码:所有代码都可直接复制运行,包含题目案例和边界情况,运行后能直观看到反转效果,帮助理解逻辑;

  5. 避坑重点:迭代法别忘保存next,递归法别忘置空next,不交换val值,分清Python和C++的"空"(None/nullptr)。

只要跟着这篇题解,从基础到代码,从运行过程到避坑指南,一步步看、一步步练,完全不懂的也能彻底学会反转链表!

注意:递归的"递"是"下去","归"是"回来",每一层递归都会保存当前节点的状态,回溯时再处理,核心是"先找到最后一个节点(新头),再从后往前反转指向"。

相关推荐
sg_knight1 小时前
第一次用 OpenClaw,我让它 3 分钟写了个小工具
算法·llm·agent·ai编程·openclaw
炸膛坦客1 小时前
嵌入式 - 数据结构与算法:(1-10)排序算法 - 冒泡排序(Bubble Sort)
算法·排序算法
无限进步_2 小时前
【C++】从红黑树到 map 和 set:封装设计与迭代器实现
开发语言·数据结构·数据库·c++·windows·github·visual studio
Hello eveybody2 小时前
介绍一下动态树LCT(Python)
开发语言·python·算法
不穿铠甲的穿山甲2 小时前
MMR最大边际相关性
算法
handler012 小时前
速通蓝桥杯省一:二分算法
c语言·开发语言·c++·笔记·算法·职场和发展·蓝桥杯
炽烈小老头2 小时前
【 每天学习一点算法 2026/05/08】最小覆盖子串
学习·算法
2501_921960852 小时前
协同本体论·离散动力学模拟:两个官方版本
数据结构·重构
汉克老师2 小时前
GESP5级C++考试语法知识(十六、分治算法(三))
c++·算法·分治算法·汉诺塔·逆序对·gesp5级·gesp五级