近期在梳理 Hot100 刷题路线时,翻转链表是我记录里来回打磨比较多的一道。很多人觉得链表反转太基础、看一眼就会,可一旦连续写几道变体,或者在面试白板上从零开始推,常常会发现中间有几个细节需要真正想清楚。这篇就围绕 Hot100 里这道高频基础题,把迭代与递归的来龙去脉、容易踩的坑、还有它背后能延伸出的区间翻转和 K 个一组翻转全部串起来,分享一些我用代码反复试错之后沉淀下来的思考方式。
1. 翻转链表是Hot100里的高频题,更是一系列进阶题的“神经元”
如果只看题目本身,翻转链表确实算不上复杂。但它频繁出现在各种热题索引里,原因并不是因为它考背诵,而是因为它是很多链表结构的“神经单元”。如果你把它变成单纯记住代码片段,那后续碰上回文链表、两两交换节点、K 个一组翻转、甚至链表排序,大概率又得从头死记一套。相反,如果在翻转链表这个题上把指针的走向、边界条件、递归返回值的语义吃透,上面那些题就会自然变成同一个模板的微调。
1.1 链表题的第一课:用指针关系代替“物理位置”
数组和链表最大的不同,在于我们对“位置”的直觉。数组按下标访问,给人感觉是一个连续排列的格子;而链表所谓的位置全靠 next 指针串联,没有物理连续。正因如此,“翻转链表”并不是把元素拷贝到新空间,而是调整每条 next 指向的源头与去向。
第一次写翻转链表时,我自己最明显的思想误区是试图画一条新链表出来:比如构造一个 dummy 节点去遍历原链表,把每个节点想办法塞到 dummy 后面。这种做法最终也能工作,但实现的往往是“头插法”的不自觉变种,而且容易引入不必要的内存分配。实际在 Hot100 的标准解法里,我们要做的事更纯粹:不新建节点,只把原链表中每条边的方向“调头”。
这就带来一个编码上的关键习惯:在动手修改 cur.next 之前,必须先把 cur.next 原本指向的下一个节点保存下来,否则修改之后,后面的节点就找不到了。这个“先锁后继”的做法贯穿整个链表操作,可以说是后续各种链表题里最通用的底层操作之一。
1.2 翻转链表为什么值得彻底弄懂而不是背熟
在做 Hot100 的过程中,我发现不少人有“背题惯性”:看到一个链表反转的答案,代码很短,就先背下来。可一旦题目多一些限制条件,比如“只翻转第 4 个节点到第 7 个节点”,或者“每两个一组翻转”,背过的代码立刻就不够用了。
翻转链表真正有价值的地方,在于它是一种“模式”:先分清当前节点、前驱节点和后继节点,然后在遍历中依次完成 保存后继 → 修改当前节点指向 → 前驱右移 → 当前右移。这个模式推演一遍之后,很多题能套用到上面。与其说它是代码模板,不如说它是你脑子里对链表操作的具象建模。只有当你能在图中把指针一行一行画出来,才算真的把这类题吃透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 迭代翻转:靠三个指针把整条链“拆开又接上”
迭代法应该是大家最先接触的解法,也是很多面试官默认要求你能流畅写出来的方案。逻辑上,它用 pre、cur、nxt 三个指针协同工作。
2.1 核心过程:保存后继,再掰弯当前节点
给定一个单链表头节点 head,我们要把它彻底反转。一开始,链表的头节点会成为新链表的尾节点,所以它的 next 最终指向 null。我们可以用 pre 表示已经翻转好的链表段的头节点,cur 表示当前待处理的节点,nxt 用于暂存 cur 原本的下一个节点。
标准的迭代代码如下:
python复制def reverseList(head):
pre = None
cur = head
while cur is not None:
nxt = cur.next # 先把下一个节点保存下来
cur.next = pre # 反转箭头:当前节点指向前一个节点
pre = cur # 前驱移动到当前节点
cur = nxt # 当前节点移动到原本的下一个节点
return pre # 循环结束时,pre 正好指向翻转后的头节点
这套代码极短,但它在每一步都有一个关键保护:nxt = cur.next 必须发生在 cur.next = pre 之前。这句话看着稀松平常,实际写代码时经常被漏掉,尤其是从递归写法切换回迭代时,容易被返回值影响。
2.2 用一条长度为 5 的链表来追踪指针
拿一个具体例子走一遍。初始链表为 1 -> 2 -> 3 -> 4 -> 5 -> null。
| 步骤 | 循环前的核心状态 | 循环后的链表大致形态 |
|---|---|---|
| 初始 | pre=None, cur=1 | 1 -> 2 -> 3 -> 4 -> 5 |
