今天是我刷 LeetCode 的第 11 天,做到第 24 题:《两两交换链表中的节点》。这题标的是 Medium,但说难不难,说简单也不简单——如果你用惯了 JavaScript 的数组和对象,第一次接触链表操作时很容易懵,因为链表题本质上考的不是 for 循环怎么写,而是你对“引用”和“指针指向”的理解。这一篇我不会只贴个能过审的答案,而是把题目背后的链表思路、迭代与递归两种解法、为什么哑节点能省掉一堆 if 判断,以及我实际调试时踩过的几个坑一起过一遍。适合刚开始刷链表、或者对指针操作还不太放心的同学,看完之后你可以照着代码自己跑一遍,再顺手做两道相似题巩固。
1. 题目拆解:两两交换到底在交换什么
1.1 题目需求与边界条件
题面本身很直白:给你一个链表,把相邻的两个节点交换位置,然后返回交换后链表的头节点。比如输入 1 -> 2 -> 3 -> 4,期望输出是 2 -> 1 -> 4 -> 3;如果链表只有一个节点,比如 1,那它没法配对,直接返回 1;如果链表为空,直接返回 null。
这里有两个容易被忽略的边界:第一,链表长度为奇数时,最后一组只有一个节点,这个节点不需要交换,保持原样就行。例如 1 -> 2 -> 3 的输出是 2 -> 1 -> 3,不是 2 -> 1,更不是把 3 丢掉。第二,交换之后链表的头节点变了,原来的 head 变成了第二个节点,所以返回值不能简单写成 head,而是要找到交换后的新头。
很多人第一次看到这题会觉得很简单:不就把 val 两两交换吗?确实,力扣判定时只检查节点的值顺序,如果你只交换 node.val,用例大概率也能过。但题目要求里明确写了“不能只是改值”,而是要真实地交换节点。我个人建议哪怕判题系统没有强制,也要坚持做“节点交换”,因为这才是这题想让你练的东西——操作 next 指针。
1.2 为什么数组思维在这里会失效
如果你平时写 JavaScript 主要操作数组,遇到这道题的第一反应可能是:先遍历一遍链表,把所有值存进数组,然后两两交换数组元素,再用数组重新构建一个链表。这个思路能过吗?大部分用例能过,而且代码写起来很简单。
但我不推荐刷题阶段这么做,原因有三点:第一,这题的空间复杂度要求是 O(1),如果用数组再重建链表,额外空间是 O(n),不符合最佳解法;第二,你练链表题本来就是为了熟悉节点引用、指针连接这些底层能力,绕过去等于白练;第三,在面试场景里,面试官大概率会追问“如果节点对象里有其他业务字段怎么办”,你只换 val 的方案立刻站不住脚。
链表和数组最大的区别在于:数组是一块连续内存,元素之间的“前后关系”是靠下标天然确定的;链表则是一连串对象,每个对象里保存着“下一个节点的引用”,关系是显式挂在节点上的。要做“交换两个节点”,本质上就是重新调整这些引用关系,让它们的前后顺序发生改变。
1.3 从示例推演看核心难点
拿 1 -> 2 -> 3 -> 4 举例。我们要把 1 和 2 交换,再把 3 和 4 交换。第一组交换前,需要考虑 1 的前一个节点是谁。如果 1 本身就是头节点,那它的前一个位置是空的,这就引出了后面要讲的哑节点问题。如果你只处理“当前节点”和“下一个节点”,不考虑前驱,交换到第二组时就会遇到麻烦:第二组的前驱是 1,而 1 在第一轮已经变成第二个节点了,这个“前驱关系”是动态变化的。
所以这道题真正的难点不是“交换”动作本身,而是交换前如何保存好各个相关节点,交换后如何把新的前驱和后继正确接回去。这也是为什么不管用迭代还是递归,核心都在处理那三四个 next 指向,而不是处理 val。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手前补课:JavaScript 链表的核心是“对象引用”
2.1 节点在 JavaScript 里到底长什么样
LeetCode 的 JavaScript 环境里,链表节点一般是这样定义的:
javascript复制function ListNode(val, next) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}
每个节点就是一个普通对象,val 存放值,next 存放另一个节点的引用。比如 1 -> 2 本质上是:
javascript复制const node2 = new ListNode(2);
const node1 = new ListNode(1, node2);
换句话说,node1.next 不是把 node2 复制了一份进去,而是说“node1 的 next 指向 node2 这个内存对象”。你可以把 next 理解成一个人手里拿着的“下一个人联系方式”,当你改变 node1.next 的指向时,不是
