场景想象:链表里的节点在排队:1 -> 2 -> 3 -> 4。 现在要求:每两个节点为一组,互换位置。
1和2交换,变成2 -> 1。3和4交换,变成4 -> 3。最后连起来:
2 -> 1 -> 4 -> 3。
核心痛点:很多同学会直接把节点的值互换(val互换),虽然能过 LeetCode,但这不是面试官想要的。面试官考的是修改指针指向。 要交换1和2,你需要操作三个步骤,而且必须小心翼翼,一旦断链就全完了。
力扣 24. 两两交换链表中的节点
https://leetcode.cn/problems/swap-nodes-in-pairs/
题目分析:
输入:链表头节点
head。目标:两两交换相邻节点。
输出:交换后的链表头节点。
核心思维:虚拟头结点 + 三步走
既然头结点1也要被换到后面去,那头结点肯定会变。口诀:头结点会变,虚拟头结点 (Dummy Head)必不可少!
我们需要一个指针temp指向要交换的两个节点前面的那个位置(初始指向 dummy)。 假设当前是temp -> 1 -> 2 -> 3。我们要交换1和2。 设node1 = 1,node2 = 2。
交换三部曲(画图奇效):
temp.next = node2(步骤一:前面的线连到 2)状态:
temp -> 2,1 -> 2 -> 3
node2.next = node1(步骤二:2 回头连 1)状态:
temp -> 2 -> 1,1 -> 2(这里形成了个小环),1 -> 3(别忘了1还指着3)
node1.next = node3(步骤三:1 连向后面的 3)状态:
temp -> 2 -> 1 -> 3。完美!
最后,temp前进到node1的位置(也就是交换后的第二个节点),准备处理下一组3和4。
代码实现 (JavaScript)
JavaScript
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @return {ListNode} */ var swapPairs = function(head) { // 1. 虚拟头结点:凡是头结点会动的题,无脑上 dummy let dummy = new ListNode(0, head); // temp 是当前操作组的“前驱节点” let temp = dummy; // 循环条件:必须要后面至少有两个节点,才能交换 // temp.next 是第一个,temp.next.next 是第二个 while (temp.next !== null && temp.next.next !== null) { // 2. 定义两个要交换的节点 let node1 = temp.next; // 第一个节点 (1) let node2 = temp.next.next; // 第二个节点 (2) // 3. 开启交换三部曲 // 步骤一:前驱指后一个 (dummy -> 2) temp.next = node2; // 步骤二:后一个指前一个 (2 -> 1) node2.next = node1; // 步骤三:前一个指下下个 (1 -> 3) // 注意:这里用 node1.next 指向 node2 原来的 next // 但此时 node2.next 已经被改了,所以要拿 temp.next.next 吗? // 不对,应该在交换前把 node3 保存下来,或者利用 node1.next 还没改的时候 // 其实最简单的是:node1.next = node2.next (这里 node2.next 已经被改成 node1 了,会死循环) // --- 修正逻辑 --- // 我们需要先保存 node3 (下一组的开头) // 或者按顺序操作,不要把自己绕晕 // 让我们重来最清晰的写法: // 先保存下一轮的起点 (3) let nextStart = node2.next; temp.next = node2; // dummy -> 2 node2.next = node1; // 2 -> 1 node1.next = nextStart; // 1 -> 3 // 4. 指针前进 // 此时链表是 dummy -> 2 -> 1 -> 3 -> 4 // 下一轮我们要站在 1 的位置,去处理 3 和 4 temp = node1; } return dummy.next; };深度模拟
假设dummy -> 1 -> 2 -> 3 -> 4
初始:
temp = dummy。检查:后面有 1 和 2。可以换。
node1 = 1,node2 = 2,nextStart = 3。连线:
dummy -> 2,2 -> 1,1 -> 3。链表变:
dummy -> 2 -> 1 -> 3 -> 4。移动:
temp跳到1。
第二轮:
temp = 1。检查:后面有 3 和 4。可以换。
node1 = 3,node2 = 4,nextStart = null。连线:
1 -> 4,4 -> 3,3 -> null。链表变:
dummy -> 2 -> 1 -> 4 -> 3 -> null。移动:
temp跳到3。
结束:
temp后面没节点了,循环结束。
总结
这道题是“多指针操作”的典型训练。
不要试图要在脑子里模拟三根线的变化,一定会晕。
一定要画图,把步骤 1、2、3 标在纸上,代码自然就写出来了。
只要掌握了这题,后面的 K 个一组翻转(LC 25)其实就是把“两两”变成了“K个”,逻辑是一模一样的。
下一题预告:删除链表的倒数第 N 个结点
练完了细致的指针操作,下一题LC 19. 删除链表的倒数第 N 个结点要考一点**“小聪明”**了。
题目:只扫描一遍链表,怎么找到倒数第 N 个节点?
技巧:快慢指针。让快指针先跑 N 步,然后两个指针一起跑。当快指针到头时,慢指针正好停在你想要的地方。
这是一种非常经典的**“尺子思维”**,准备好去测量链表的长度了吗?