一、题目重述
给你一个单链表的头节点 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 链表的遍历
遍历就是"从头到尾,一个个看链表的每个节点",比如我们想打印链表的所有值,思路就是:
-
从 head(头节点)开始;
-
先打印当前节点的值;
-
通过当前节点的next,找到下一个节点;
-
重复步骤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→空)为例:
-
初始状态:prev = 空,curr = 1(head),nextTemp = 空;
-
第一步:保存curr的下一个节点(nextTemp = curr.next = 2),防止断链;
-
第二步:反转curr的指向(curr.next = prev = 空),此时1的next指向空;
-
第三步:移动指针(prev = curr = 1,curr = nextTemp = 2),准备处理下一个节点;
-
重复步骤2-4,直到curr为空(处理完所有节点);
-
最终,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个指针的变化:
-
初始状态:prev = nullptr,curr = 1,nextTemp = nullptr;
-
第一次循环(curr=1):
-
nextTemp = curr->next = 2;
-
curr->next = prev = nullptr(此时1→空);
-
prev = curr = 1,curr = nextTemp = 2;
-
当前链表状态:1→空,2→3→4→5→空(prev指向1,curr指向2)。
-
-
第二次循环(curr=2):
-
nextTemp = curr->next = 3;
-
curr->next = prev = 1(此时2→1→空);
-
prev = curr = 2,curr = nextTemp = 3;
-
当前链表状态:2→1→空,3→4→5→空(prev指向2,curr指向3)。
-
-
第三次循环(curr=3):
-
nextTemp = 4;
-
curr->next = 2(3→2→1→空);
-
prev=3,curr=4;
-
-
第四次循环(curr=4):
-
nextTemp = 5;
-
curr->next = 3(4→3→2→1→空);
-
prev=4,curr=5;
-
-
第五次循环(curr=5):
-
nextTemp = nullptr(5是最后一个节点,next为空);
-
curr->next = 4(5→4→3→2→1→空);
-
prev=5,curr=nextTemp = nullptr;
-
-
循环结束(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++完全一样,只是语法不同:
-
初始状态:prev = None,curr = 1(head),next_temp = None;
-
第一次循环(curr=1):
-
next_temp = curr.next = 2;
-
curr.next = prev = None(1→None);
-
prev = 1,curr = 2;
-
-
第二次循环(curr=2):
-
next_temp = 3;
-
curr.next = 1(2→1→None);
-
prev=2,curr=3;
-
-
第三次循环(curr=3):next_temp=4,curr.next=2(3→2→1→None),prev=3,curr=4;
-
第四次循环(curr=4):next_temp=5,curr.next=3(4→3→2→1→None),prev=4,curr=5;
-
第五次循环(curr=5):next_temp=None,curr.next=4(5→4→3→2→1→None),prev=5,curr=None;
-
循环结束,返回prev=5,反转完成。
⚠️ 注意:Python里的"None"和C++里的"nullptr"完全一样,都是"空"的意思;Python的"curr is not None"和C++的"curr != nullptr"是同一个逻辑。
四、解法二:递归法(进阶!代码极简,面试加分,理解需多花点时间)
递归法的核心思路:"递到最后,再归回来"。简单说就是:先一路递归到链表的最后一个节点(这个节点就是反转后的新头节点),然后在"回溯"的过程中,逐个反转每个节点的指向。
递归的关键(记死):
-
递(下去):一直递归,直到找到链表的最后一个节点(终止条件:当前节点为空,或当前节点是最后一个节点);
-
归(回来):回溯时,让当前节点的"下一个节点"的next,指向当前节点,然后把当前节点的next置为空(防止链表成环);
-
全程返回"新头节点"(也就是链表的最后一个节点,递归过程中这个节点一直不变)。
举个直白的例子(还是1→2→3→4→5→空):
-
递:reverseList(1) → reverseList(2) → reverseList(3) → reverseList(4) → reverseList(5);
-
终止:reverseList(5)时,5是最后一个节点(5.next为空),返回5(新头节点);
-
归:
-
回到reverseList(4):此时当前节点是4,下一个节点是5(刚才返回的新头),让5.next = 4,4.next = None(此时4→5→None),返回5;
-
回到reverseList(3):当前节点是3,下一个节点是4,让4.next = 3,3.next = None(3→4→5→None),返回5;
-
回到reverseList(2):当前节点是2,下一个节点是3,让3.next = 2,2.next = None(2→3→4→5→None),返回5;
-
回到reverseList(1):当前节点是1,下一个节点是2,让2.next = 1,1.next = None(1→2→3→4→5→None 反转成 5→4→3→2→1→None),返回5;
-
-
最终返回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→空)为例,拆解递归的每一步:
-
调用 reverseList(head=1):
-
判断:1 != nullptr,1->next=2 != nullptr → 不满足终止条件;
-
调用 reverseList(head=2),进入下一层递归。
-
-
调用 reverseList(head=2):
-
判断:2 != nullptr,2->next=3 != nullptr → 不满足终止条件;
-
调用 reverseList(head=3),进入下一层递归。
-
-
调用 reverseList(head=3):
-
判断:3 != nullptr,3->next=4 != nullptr → 不满足终止条件;
-
调用 reverseList(head=4),进入下一层递归。
-
-
调用 reverseList(head=4):
-
判断:4 != nullptr,4->next=5 != nullptr → 不满足终止条件;
-
调用 reverseList(head=5),进入下一层递归。
-
-
调用 reverseList(head=5):
-
判断:5 != nullptr,但5->next=nullptr → 满足终止条件;
-
返回5(newHead=5),回到上一层(reverseList(4))。
-
-
回溯到 reverseList(4):
-
此时 newHead=5,当前节点head=4;
-
head->next->next = head → 4->next(5)->next = 4 → 5->next=4;
-
head->next = nullptr → 4->next=nullptr(此时4→5→空);
-
返回newHead=5,回到上一层(reverseList(3))。
-
-
回溯到 reverseList(3):
-
newHead=5,当前节点head=3;
-
3->next(4)->next = 3 → 4->next=3;
-
3->next=nullptr(此时3→4→5→空);
-
返回5,回到上一层(reverseList(2))。
-
-
回溯到 reverseList(2):
-
newHead=5,当前节点head=2;
-
2->next(3)->next = 2 → 3->next=2;
-
2->next=nullptr(此时2→3→4→5→空);
-
返回5,回到上一层(reverseList(1))。
-
-
回溯到 reverseList(1):
-
newHead=5,当前节点head=1;
-
1->next(2)->next = 1 → 2->next=1;
-
1->next=nullptr(此时1→2→3→4→5→空 反转成 5→4→3→2→1→空);
-
返回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++逻辑完全一样,只是语法不同,逐步拆解如下,跟着走一遍就能懂:
-
调用 sol.reverseList(head=1)(测试代码中调用,进入核心函数): 判断:head=1 不为None,head.next=2 也不为None → 不满足终止条件;
-
调用 self.reverseList(head=2),进入下一层递归(递下去)。
-
调用 self.reverseList(head=2): 判断:head=2 不为None,head.next=3 不为None → 不满足终止条件;
-
调用 self.reverseList(head=3),进入下一层递归。
-
调用 self.reverseList(head=3): 判断:head=3 不为None,head.next=4 不为None → 不满足终止条件;
-
调用 self.reverseList(head=4),进入下一层递归。
-
调用 self.reverseList(head=4): 判断:head=4 不为None,head.next=5 不为None → 不满足终止条件;
-
调用 self.reverseList(head=5),进入下一层递归(递到最底层)。
-
调用 self.reverseList(head=5): 判断:head=5 不为None,但 head.next=None(5是最后一个节点) → 满足终止条件;
-
返回 head=5(这就是新头节点new_head),回到上一层递归(reverseList(4))。
-
回溯到 self.reverseList(head=4): 此时 new_head=5(上一层返回的结果),当前节点head=4;
-
执行 head.next.next = head → 4的next是5,所以5.next = 4(此时5→4);
-
执行 head.next = None → 4.next = None(此时4→5→None,避免成环);
-
返回 new_head=5,回到上一层递归(reverseList(3))。
-
回溯到 self.reverseList(head=3): new_head=5,当前节点head=3;
-
head.next.next = head → 3的next是4,所以4.next = 3(此时4→3);
-
head.next = None → 3.next = None(此时3→4→5→None);
-
返回 new_head=5,回到上一层递归(reverseList(2))。
-
回溯到 self.reverseList(head=2): new_head=5,当前节点head=2;
-
head.next.next = head → 2的next是3,所以3.next = 2(此时3→2);
-
head.next = None → 2.next = None(此时2→3→4→5→None);
-
返回 new_head=5,回到上一层递归(reverseList(1))。
-
回溯到 self.reverseList(head=1): new_head=5,当前节点head=1;
-
head.next.next = head → 1的next是2,所以2.next = 1(此时2→1);
-
head.next = None → 1.next = None(此时1→2→3→4→5→None 反转成 5→4→3→2→1→None);
-
返回 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:忘记保存next节点,导致链表断链 解决:迭代法中,必须先保存curr.next(nextTemp/next_temp),再修改curr.next的指向,这一步是核心,不能颠倒顺序。
-
坑2:递归法忘记将当前节点的next置为None,导致链表成环 解决:回溯时,除了让head.next.next = head,必须加上head.next = None,否则会出现1→2→1的死循环(链表成环)。
-
坑3:混淆"空链表"和"单节点链表"的边界情况 解决:两种方法都已经处理了这两种情况(迭代法循环不执行,递归法触发终止条件),不用额外加判断,直接用代码即可。
-
坑4:Python中把None写成null,C++中把nullptr写成None 解决:记死:Python用None,C++用nullptr,两者不能混用,否则会报错。
-
坑5:试图通过交换节点val值来反转链表 解决:题目核心要求是"反转节点连接关系",不允许交换val值,即使能得到正确结果,也不符合题目要求(面试会扣分)。
七、总结(必看,快速梳理重点)
-
前置知识:先搞懂「单链表结构」「节点的val和next属性」「指针/引用的含义」,这是看懂代码的基础,没懂的话回头再看第二部分;
-
解法选择:优先学「迭代法」,3个指针(prev、curr、nextTemp)一步步反转,记准"保存→反转→移动"的步骤,就能掌握;
-
递归法:进阶学习,核心是"递到最后,归回来反转",记住终止条件和回溯时的两步操作(反转指向+置空next);
-
测试代码:所有代码都可直接复制运行,包含题目案例和边界情况,运行后能直观看到反转效果,帮助理解逻辑;
-
避坑重点:迭代法别忘保存next,递归法别忘置空next,不交换val值,分清Python和C++的"空"(None/nullptr)。
只要跟着这篇题解,从基础到代码,从运行过程到避坑指南,一步步看、一步步练,完全不懂的也能彻底学会反转链表!
注意:递归的"递"是"下去","归"是"回来",每一层递归都会保存当前节点的状态,回溯时再处理,核心是"先找到最后一个节点(新头),再从后往前反转指向"。