刷算法题这种事儿,说实话大部分题目做完一周不看基本就忘光了,可 206 反转链表偏偏是那类你忘了解法也能自己推出来的题。正因为它足够基础、解法足够多,每次重刷都能看出自己之前的理解停留在哪个层次。这次我重刷 206,不光是默写一遍代码,而是把迭代、递归、头插几种思路认真捋了一遍,顺便把边界条件和常见变体也过了一下。这篇文章就当是我自己的二刷笔记,同时也适合准备面试或者刚开始刷链表专题的朋友参考。
1. 整体设计与思路拆解:为什么 206 值得一刷再刷
1.1 重刷到底在刷什么
很多人对重刷的理解就是“再做一遍,把代码背熟”。我刷了这么多题之后发现,重刷的真正价值在于做三件事:第一,检验自己能不能在完全不看题解的前提下,从问题描述推导出解法;第二,对比不同解法的代码量和运行效率,养成从多个角度思考的习惯;第三,把一道简单题当成跳板,串联起它背后的一整类链表操作问题。
206 反转链表恰好能承担以上所有任务。链表的反转操作是后续很多题目的公共底座,比如反转链表的前 N 个节点、区间反转、每 K 个一组反转,甚至回文链表的判断,本质上都是在反转局部链表。能把这个底座打得越扎实,后面做中难题就越省力。
1.2 解法全貌与核心矛盾
反转链表的主流解法有三种:迭代法、递归法、头插法(借助新链表头)。另外还有人用栈来做,但是栈做的话空间复杂度会退化成 O(n),所以在面试中优先级不高。
这三种解法表面上看是“不同实现”,实际反映的是同一个核心矛盾:单向链表只能从 head 往后走,不能回头。而反转相当于要让每个节点都回头指向自己的前一个节点。怎么在“不能回头”的物理限制下实现“回头”的动作?迭代用三个指针轮流替换,递归用系统调用栈帮你记住前驱节点,头插法则是把每个节点一个一个摘下来挂到新头前面。理解了这一层,你写的就不是代码,而是思路本身。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点:链表的指针操作三坎
2.1 迭代法的 prev、curr、next 三指针是怎么动起来的
迭代法是反转链表的首选方案,代码短、空间 O(1)、思路直观。它的核心是维护两个指针:prev 和 curr。初始时 prev 指向 nullptr,curr 指向 head。每轮循环做四件事:
- 用 next 暂存 curr 的下一个节点(防止断链);
- 把 curr 的下一个节点指向 prev(完成一次反转);
- 把 prev 移动到 curr(prev 前进);
- 把 curr 移动到 next(curr 前进)。
这里最容易被忽略的其实是第一步。很多人第一次写反转链表,会把 curr->next = prev 当成唯一核心操作,忘了提前保存原来的 curr->next,造成链表在第一个节点处就断掉了。这是因为一旦 curr->next 被改写,原本的链表后续部分就没人能访问到了。
我个人的经验是,在写链表操作之前,心里默默过一遍“这个指针被改之后,还有没有其他变量能访问到它原来指向的节点”——如果答案是没有,那你必须先用一个临时变量把它保存下来。这是链表指针操作的一条通用规则,206 不是唯一用到它的地方。
2.2 递归法的递推关系是什么
递归法的代码看起来更短,但理解门槛反而更高。它的核心不是“循环反转”,而是“把问题缩小”。
反转整个链表,可以拆成两步:先反转从第二个节点开始的子链表,再把原来位于 head 的节点接到反转后子链表的末尾。注意这里的关键:子链表反转完成后,它的尾节点恰好就是原来 head 的下一个节点,也就是 head->next。所以我们要做的操作是:
cpp复制ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
解释下这两行:head->next->next = head 相当于让原来 head 的下一个节点反转后
