1. 题目理解与核心难点:为什么这道题适合卡在第11天
这是 Leetcode 24 题“两两交换链表中的节点”,也是我刷题打卡第 11 天遇到的题。说实话,Leetcode 前二十题里,链表相关题目看着简单,但实际动手写的时候特别容易翻车。这道题的标签是链表、递归、指针操作,用 JavaScript 实现时,难点反而不是语法,而是你能不能想清楚“当前节点到底是谁在指向谁”。很多朋友一开始会把交换两个节点的值理解成数组里的两两交换,直接改 val,这是走偏了。链表的结构性操作练的就是“改指针”,而不是“改值”,题目里真正想考察的也是前者。
这道题适合刚学会链表遍历、想进一步理解链表引用关系的人,也非常适合作为“指针操作入门题”来刷。它不涉及复杂算法,但是把链表里最核心的“重定向顺序”和“前置节点保存”这两个思想全包含了。如果能不看题解,独立写对迭代和递归两个版本,那说明你链表基础基本过关了。
另外,用 JavaScript 刷链表有个隐性门槛:我们平时写数组写习惯了,总觉得“对象赋值就是复制一份”,但链表节点本质是对象,变量存的是引用。操作链表就是操作一堆相互嵌套的 JS 对象,改 next 时如果顺序不对,几步之后整条链就会断掉或成环,而且控制台还不一定报错。所以借这道题,我正好把“JS 里对象引用到底怎么影响链表操作”这个底层问题彻底讲透。
1.1 题目到底在要求什么
给一个链表,比如 1 -> 2 -> 3 -> 4,要求每两个相邻节点交换位置,最终变成 2 -> 1 -> 4 -> 3。如果链表长度是奇数,最后一个节点保持不动,比如 1 -> 2 -> 3 变成 2 -> 1 -> 3。
注意几个边界条件:空链表返回空;只有一个节点,直接返回它自己;只有两个节点,交换后返回新的头节点。这些边界条件在 LeetCode 的测试用例中一定会覆盖,跑挂大多也是挂在这些地方。
题目要求“不能只是修改节点内部的值,而是要真正交换节点”。也就是说,不要写出下面这种取巧代码:
js复制var swapPairs = function(head) {
let cur = head;
while (cur && cur.next) {
[cur.val, cur.next.val] = [cur.next.val, cur.val];
cur = cur.next.next;
}
return head;
};
这段代码在 LeetCode 上其实能通过,因为判题机只看最终链表的值。但假如面试官追问“如果节点内部还有别的字段,你这么换会有什么问题?”你会发现这样写没有真正改变节点之间的链接关系。一般刷题训练不推荐这么做,因为后续做“反转链表 II”“K 个一组翻转链表”这类题目时,依赖的恰恰是改指针的思路,而不是投机取巧改值。
1.2 LeetCode 24 的核心考点与高频场景
这道题虽然难度是中等偏简单,但它是很多公司面试题的前置热身题。高频场景一般有几种:一是直接考原题,让你写递归;二是考“两两交换”的升级版,比如 K 个一组翻转链表;三是在系统设计面试中引申到“链表中相邻节点交换背后的引用传递问题”,考察你对 JS 对象引用的理解。
我自己面试别人的时候,也喜欢让候选人先写这道题。原因很简单:它可以快速区分三种人。
- 第一种人,连题目意思都要理解半天,说明链表基础比较薄弱;
- 第二种人,能写出来,但上来就改 val,说明没有理解链表的本质;
- 第三种人,能写出结构修改的正确解法,并且能把递归版本写清楚,说明对指针和函数调用栈有比较好的感觉。
所以别小看这题。把 Leetcode 24 吃透,后面做反转链表、删除倒数第 N 个节点,会顺利很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 迭代解法:虚拟头节点把“指针游戏”理顺
迭代解法比较符合人脑的思考方式:从头到尾,每次处理两个节点,交换完往前移动。但是第一次写的人经常会遇到一个问题:交换完前两个节点后,头节点变了,你怎么把它返回出去?如果直接 return head,返回的还是原来的头节点,而原来的头节点已经被交换到第二位了。这时候就需要虚拟头节点出场。
2.1 虚拟头节点到底解决了什么问题
虚拟头节点(dummy node)是一个额外创建的空节点,它的 next 指向真正的头节点 head。不管链表内部怎么交换,dummy.next 始终指向整条链表最新的头节点,最后直接返回 dummy.next 就行。
很多新手不理解为什么要多创建一个节点,觉得多此一举。实际上它的核心作用有两个:
- 统一处理“头节点被修改”的情况,让代码循环部分不用为“头节点没有前驱”单独写分支;
- 配合一个 prev 游标,记录当前处理到的位置的前一个节点,保证后续操作不会丢失前面的链。
这有点像是在队伍前面站了一个“哨兵”,真正的队伍怎么变化,你只要看哨兵指向谁,就知道队首是谁。
以 1 -> 2 -> 3 -> 4 -> null 为例:
js复制dummy -> 1 -> 2 -> 3 -> 4 ->
