在LeetCode上刷链表题的人,十有八九都会在“反转链表”这道题上停一会儿。明明题目描述只有一句话,代码也就十行上下,但真要自己动手写,很多人在白纸上画了半天指针,最后还是绕晕了。我当年面一家做基础架构的团队时,面试官现场让我写这道题,我用了递归交了第一版,他追问了一句“这个递归的空间复杂度是多少”,我愣了一下才反应过来,递归虽然好看,但调用栈会吃掉O(n)的额外空间。那次之后我把这道题的两种解法认认真真推了几遍,才发现以前很多理解都是“背代码背出来的”,不是真懂。
这篇东西就是想把反转链表讲透,覆盖迭代和递归两条路线,会把指针怎么移动、边界条件怎么定、返回值为什么是那样、面试官追问时该怎么应对都讲清楚。无论你是刚接触链表的初学者,还是准备面试刷题的老手,这篇文章都能给你一些不一样的视角。
1. 题面拆解:反转链表到底在反转什么
1.1 链表的存储方式决定了“反转”不是一件直觉的事
数组反转很简单:知道长度,首尾下标一换就行,因为数组在内存里是连续存放的,下标就是地址。但链表不一样,每个节点是一个独立的对象,除了存自己的数值 val,还维护一个指向下一个节点的指针 next。链表在内存里未必连续,节点之间靠指针“串”起来。
所以反转链表的本质,不是把节点里的数值倒过来排,而是要重新编排每个节点 next 指针的指向。原来指向后一个节点,现在要指向前一个节点。换句话说,整条链的“方向”被拧反了。
这个描述听起来简单,但有一个天然瓶颈:单链表节点只存了“下一个节点”在哪,没有存“上一个节点”在哪。你站在当前节点上,只能往后走,不能回头看。要修改当前节点的 next,就必须提前把“前一个节点”记下来。这个“记下来”的动作,就是所有解法的核心起点。
1.2 输入约束和边界情况:空链表、单节点、多节点
力扣的题目一般会给出 ListNode 的定义:
cpp复制struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
输入是链表的头节点 head,要返回反转后的新头节点。这里有三个边界情况:
- 空链表(
head == nullptr):没什么可反转的,直接返回空。 - 只有一个节点(
head->next == nullptr):反转前后一样,直接返回head。 - 多个节点:正常走反转逻辑。
这三种情况其实可以在同一个判断里兜住:如果 head == nullptr || head->next == nullptr,直接返回 head。这也是递归解法的 base case。
很多人面试时一上来就写循环,结果输入是空链表时直接段错误,就是因为在最开始没有处理边界。别看这是个细节,我在实际面试别人时,几乎每三份反转链表的代码里就有一份会把边界漏掉。
1.3 一个容易被忽视的问题:为什么要返回“新头节点”
这里想提醒一下:反转之后,原来的尾节点变成了头节点,原来的头节点变成了尾节点。返回值如果还是原来的 head,那返回的链表只是从原尾部开始的单节点或者空链表,整条链从中断开了。所以最终返回值一定是“新的头”,也就是反转前链表的最后一个节点。
这也是迭代法和递归法在代码层面容易出错的地方。迭代法里新头是循环结束时的 prev,递归法里新头是递归最深处返回的那个节点。后面两部分我会分别推到这一步,现在先有一个印象即可。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 迭代解法:三个指针的动态推演
2.1 为什么要三个指针
先想一个最朴素的目标:从头到尾走一趟,每走到一个节点,就把它的 next 指向前一个节点。那“前一个节点”从哪来?只能拿变量存着。
假设有两个变量:prev 存前一个节点,curr 存当前节点。走到 curr 时,如果直接把 curr->next = prev,坏事了,curr->next 本来指向的是原链表的下一个节点,这一改,后面的节点就全丢了,链表等于从中间断掉。
所以要赶在修改 curr->next 之前,先把下一个节点取出来,存到 next 里。这样即使当前节点的 next 被改掉了,我们依然可以通过 next 往下走。
于是就有了经典的“三指针”结构:
prev:指向当前节点的前驱,初始为nullptr。curr:当前要处理反转的节点,初始为head。next:保存当前节点的原始后继,防止断链。
2.2 迭代步骤:每一步都在做什么
用链表 1 -> 2 -> 3 -> nullptr 来走一遍完整过程。
初始状态:
code复制prev = nullptr
curr = 1
第一步循环:
next = curr->next,也就是next = 2。curr->next = prev,也就是1 -> nullptr。prev = curr,prev从 `n
