LEETCODE 206Easy
反转链表
反转的本质是逐个改写 next 指针,难点在于改指针之前别把后继节点弄丢。
问题拆解
要把 1 -> 2 -> 3 -> 4 -> 5 变成 5 -> 4 -> 3 -> 2 -> 1。链表不像数组能随机访问,只能顺着 next 一步步走,所以反转的动作只能落在“改写每个节点的 next 指向”上。
问题在于:当我们把 cur.next 从指向后继改成指向前驱时,原来的后继就找不到了。所以真正的关键是在改指针之前,先用一个变量把后继存下来。
反转链表不需要额外的容器,需要的是三个指针:已经反转好的前半段、当前节点、还没处理的后继。
迭代:三指针
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
while (cur != null) {
ListNode nxt = cur.next;
cur.next = prev;
prev = cur;
cur = nxt;
}
return prev;
}
def reverseList(head):
prev = None
cur = head
while cur:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return prev
func reverseList(head *ListNode) *ListNode {
var prev *ListNode
cur := head
for cur != nil {
nxt := cur.Next
cur.Next = prev
prev = cur
cur = nxt
}
return prev
}
pub fn reverse_list(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
let mut prev = None;
let mut cur = head;
while let Some(mut node) = cur {
cur = node.next.take(); // 先保住后继
node.next = prev; // 再掉头
prev = Some(node);
}
prev
}
prev 是已经反转好的部分的头,初始为 None(反转后原来的头会变成尾,尾的 next 正是 None)。每一轮先存后继,再让当前节点掉头指向 prev,然后两个指针一起前移。循环结束时 cur 走到空,prev 恰好停在原链表的最后一个节点,也就是新的头。
递归
题目进阶希望两种写法都掌握。递归的思路是:先反转从 head.next 开始的子链表,拿回它的新头,再把 head 接到子链表的末尾。
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
def reverseList(head):
if head is None or head.next is None:
return head
new_head = reverseList(head.next)
head.next.next = head
head.next = None
return new_head
func reverseList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
newHead := reverseList(head.Next)
head.Next.Next = head
head.Next = nil
return newHead
}
// Box 的独占所有权不允许“后继反过来指向自己”这种临时双向引用,
// 所以 Rust 的递归版换一种等价拆法:每层摘下头节点,接到已反转部分的前面。
pub fn reverse_list(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
fn helper(cur: Option<Box<ListNode>>, prev: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
match cur {
None => prev,
Some(mut node) => {
let rest = node.next.take();
node.next = prev;
helper(rest, Some(node))
}
}
}
helper(head, None)
}
关键的两行是 head.next.next = head(让后继反过来指向自己)和 head.next = None(断开原来的正向指针,否则会形成环)。new_head 在整条递归里一路向上原样返回,始终是原链表的尾节点。
复杂度
| 指标 | 复杂度 | 原因 |
|---|---|---|
| 时间 | O(n) |
每个节点只访问一次 |
| 空间 | O(1) / O(n) |
迭代只用常数指针;递归的栈深度是链表长度 |
可以迁移的模式
- 需要原地改写指针时,先用临时变量保住后继,再动手;
prev / cur / nxt三指针是链表操作的基本手型;- 递归写法把“反转整条”拆成“反转子链表 + 接回当前节点”。
一旦习惯了“存后继—改指针—前移”这套动作,后面很多链表题都是它的变体。