题目描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
-
链表中节点的数目范围是
[0, 5000] -
-5000 <= Node.val <= 5000
进阶: 链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
解题思路
反转链表的核心思想是:改变每个节点的 next 指针方向,让它指向前一个节点。
我们依次用两种方法来实现,并重点剖析递归的执行过程。
方法一:迭代法
思路
-
初始化三个指针:
-
pre:指向已经反转好的链表头(初始为null) -
cur:指向当前待反转的节点(初始为head) -
temp:暂存cur.next,防止链表断开后丢失后续节点
-
-
遍历链表,每一步:
-
保存
cur.next到temp -
将
cur.next指向pre(完成当前节点的反转) -
移动
pre到cur -
移动
cur到temp
-
-
当
cur为null时,遍历结束,pre就是反转后链表的新头节点。
代码
javascript
var reverseList = function(head) {
let pre = null;
let cur = head;
let temp;
while (cur !== null) {
temp = cur.next; // 保存下一个节点
cur.next = pre; // 当前节点指向前一个节点
pre = cur; // pre 前移
cur = temp; // cur 前移
}
return pre;
};
复杂度分析
-
时间复杂度:O(n),每个节点遍历一次
-
空间复杂度:O(1),只使用了常数个指针
方法二:递归法
递归法相比迭代法更抽象,但代码极其简洁。理解递归的关键是明确递归函数的定义。
递归函数定义
我们定义递归函数 reverse(cur, pre):
-
功能 :反转以
cur为当前节点,pre为前一个节点的链表 -
返回值:反转后的链表的新头节点
递归过程详解
假设链表为 1 -> 2 -> 3 -> 4 -> null,初始调用 reverse(head, null)。
步骤拆解
-
第一层调用
reverse(1, null)-
cur = 1,pre = null -
cur !== null,执行:-
temp = cur.next→temp = 2 -
cur.next = pre→ 将节点1的next指向null(完成反转) -
返回
reverse(temp, cur)即reverse(2, 1)
-
-
此时链表状态:
1 -> null,节点2暂由temp保留。
-
-
第二层调用
reverse(2, 1)-
cur = 2,pre = 1 -
保存
temp = cur.next = 3 -
cur.next = pre→ 节点2的next指向1 -
返回
reverse(3, 2)
-
-
第三层调用
reverse(3, 2)-
cur = 3,pre = 2 -
temp = 4 -
cur.next = pre→ 节点3的next指向2 -
返回
reverse(4, 3)
-
-
第四层调用
reverse(4, 3)-
cur = 4,pre = 3 -
temp = null -
cur.next = pre→ 节点4的next指向3 -
返回
reverse(null, 4)
-
-
第五层调用
reverse(null, 4)cur === null,递归终止,返回pre = 4
回溯返回
- 第五层返回
4给第四层,第四层直接返回4给第三层......最终第一层返回4。
此时链表已经变成了 4 -> 3 -> 2 -> 1 -> null,头节点是 4。
递归调用栈示意图
javascript
reverse(1, null)
│ temp = 2, 1.next = null
│ return reverse(2, 1)
│
└── reverse(2, 1)
│ temp = 3, 2.next = 1
│ return reverse(3, 2)
│
└── reverse(3, 2)
│ temp = 4, 3.next = 2
│ return reverse(4, 3)
│
└── reverse(4, 3)
│ temp = null, 4.next = 3
│ return reverse(null, 4)
│
└── reverse(null, 4) → 返回 4
← 返回 4
← 返回 4
← 返回 4
代码实现
javascript
var reverseList = function(head) {
function reverse(cur, pre) {
if (cur === null) {
return pre; // 递归到末尾,pre 就是反转后的头节点
}
let temp = cur.next; // 保存下一个节点
cur.next = pre; // 当前节点指向前一个节点
return reverse(temp, cur); // 继续反转剩余部分
}
return reverse(head, null);
};
递归法注意点
-
递归终止条件 :
cur === null时,说明已经处理完所有节点,返回pre作为新头。 -
原地修改 :每次递归只改变当前节点的
next指向,没有额外创建节点。 -
栈深度:递归深度等于链表长度,当链表过长(如 5000 节点)时,可能导致栈溢出。但在题目范围内是安全的。
复杂度分析
-
时间复杂度:O(n),每个节点递归处理一次
-
空间复杂度:O(n),递归调用栈需要 O(n) 的额外空间
总结
| 方法 | 优点 | 缺点 |
|---|---|---|
| 迭代 | 空间复杂度 O(1),效率高 | 代码稍显繁琐 |
| 递归 | 代码简洁,逻辑清晰 | 空间复杂度 O(n),链表过长有栈溢出风险 |
本题是链表操作的入门题,掌握迭代和递归两种写法,能帮助理解指针操作和递归思想。建议初学者先理解迭代法,再逐步消化递归法,重点体会递归函数"不关心中间过程,只相信子调用能完成剩余部分"的思维方式。