好的,我们这次来详细讲解 LeetCode 第 206 题「反转链表」。
1. 题目描述
题目:给你单链表的头节点 head,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
链表定义(题目已给出):
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
2. 题意理解
链表是由节点组成的,每个节点保存一个值和一个指向下一个节点的指针。
初始状态:
1 → 2 → 3 → 4 → 5 → None
反转后要变成:
5 → 4 → 3 → 2 → 1 → None
要求:
- 只使用常数额外空间(即 O(1) 额外空间,递归栈空间不算常数的特殊情况这里先不讨论递归版本的空间问题)。
- 不得改变节点内部的值,只能改变节点的
next指针。
3. 思路演进
3.1 直观想法(但不可行)
如果允许修改节点的值,我们可以先遍历链表把值存到数组,再反向填入链表。但题目一般期望直接通过指针操作完成。
3.2 关键观察
反转链表的过程,可以想象成一边遍历原链表,一边把每个节点指向前一个节点。
我们需要:
- 记录当前节点
cur和前一个节点prev。 - 在改变
cur.next之前,先保存cur原本的下一个节点nxt,否则会丢失后续链表。 - 将
cur.next指向prev。 prev和cur都向前移动一步。
4. 逐步推导
我们以 head = [1,2,3,4,5] 为例。
初始状态:
prev = None
cur = 1 → 2 → 3 → 4 → 5 → None
第 1 步:
nxt = cur.next即nxt = 2cur.next = prev即1 → Noneprev = cur即prev = 1cur = nxt即cur = 2
此时链表被拆成两部分:
反转部分:1 → None(prev 指向 1)
未反转部分:2 → 3 → 4 → 5 → None(cur 指向 2)
第 2 步:
nxt = cur.next即nxt = 3cur.next = prev即2 → 1prev = cur即prev = 2cur = nxt即cur = 3
此时:2 → 1 → None(prev 指向 2),未反转部分 3 → 4 → 5 → None
第 3 步:
nxt = 4cur.next = prev即3 → 2prev = 3cur = 4
此时:3 → 2 → 1 → None
第 4 步:
nxt = 5cur.next = prev即4 → 3prev = 4cur = 5
此时:4 → 3 → 2 → 1 → None
第 5 步:
nxt = Nonecur.next = prev即5 → 4prev = 5cur = None
此时:5 → 4 → 3 → 2 → 1 → None,cur 为 None,循环结束。
最终返回 prev,即新链表的头节点 5。
5. 代码实现(迭代法)
def reverseList(head: ListNode) -> ListNode:
prev = None
cur = head
while cur:
# 保存下一个节点
nxt = cur.next
# 反转指针
cur.next = prev
# 移动 prev 和 cur
prev = cur
cur = nxt
return prev
复杂度分析:
- 时间复杂度:O(n),遍历一次链表。
- 空间复杂度:O(1),只用了几个指针变量。
6. 递归解法(辅助理解)
递归思路:
假设头节点是 head,剩余部分 head.next 已经通过递归被正确反转了,那么我们只需要把 head 放到已反转部分的末尾。
具体:
- 递归基:如果
head是None或head.next是None,直接返回head。 - 递归反转
head.next开始的链表,得到新头节点new_head,此时head.next是新链表的尾节点。 - 把
head接在head.next的后面:head.next.next = head - 将
head.next设为None,避免成环。 - 返回
new_head。
代码:
def reverseListRecursive(head: ListNode) -> ListNode:
if not head or not head.next:
return head
new_head = reverseListRecursive(head.next)
head.next.next = head
head.next = None
return new_head
递归过程需要画图理解指针变化,但迭代法更直观且空间更优。
7. 总结
- 核心:用两个指针
prev和cur一边遍历一边反转。 - 关键:在修改
cur.next前先保存cur.next。 - 结果:最后
prev就是新链表的头节点。
这样讲解是否清晰?如果需要我画出每一步的链表指针变化图,我也可以为你绘制。