24. 两两交换链表中的节点 (Medium)

专题归类: 04-链表 LeetCode 链接: https://leetcode.cn/problems/swap-nodes-in-pairs/


在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode

题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部值的情况下完成本题(即只能进行节点交换)。

示例:

  • 输入:head = [1,2,3,4]
  • 输出:[2,1,4,3]

题目详细分析

  • 数据范围: 链表中节点数在 [0, 100] 范围内,节点值在 [0, 100] 范围内
  • 输入特征: 单链表,需要成对交换,不修改节点值
  • 核心约束: 必须实际交换节点(修改 next 指针),不能只交换节点值
  • 隐藏条件: 如果节点数是奇数,最后一个节点保持不变。每对交换涉及三个连接的修改
  • 边界条件: 空链表、只有一个节点、奇数个节点(最后一个不需要交换)

小白版直白理解

就像把一排人两两分组,每一组的两个人交换位置。第一个人站到第二个人的位置,第二个人站到第一个人的位置。

因为链表只有单向的「指向」关系,不能直接把两个人互换那么粗暴。我们需要小心地重新系好每一根绳子。为了不搞乱,最好在纸上画图,标清楚每一步指针应该怎么重新指向。


解题思路

思路一:迭代法 + 虚拟头节点(推荐)

为什么这样想: 每次交换两个节点,需要知道这两个节点的前驱。第一对没有前驱,所以用虚拟头节点作为统一前驱。画图理解指针重指向是关键。

关键洞察: 交换一对节点 (p1, p2) 涉及 3 条指针的修改:

  1. pre.next 指向 p2(p2 成为第一个)
  2. p1.next 指向 p2.next(p1 与后续连接)
  3. p2.next 指向 p1(p2 指向 p1)
def swapPairs(head):
    """迭代法:虚拟头节点 + 三步交换"""
    dummy = ListNode(0, head)
    pre = dummy
    
    # 每次检查是否有两个节点可以交换
    while pre.next and pre.next.next:
        p1 = pre.next       # 第一个节点
        p2 = p1.next        # 第二个节点
        
        # 三步交换
        pre.next = p2       # ① 前驱指向 p2
        p1.next = p2.next   # ② p1 指向 p2 的后继
        p2.next = p1        # ③ p2 指向 p1(完成交换)
        
        pre = p1            # pre 移到下一对的前驱(即 p1)
    
    return dummy.next

思路二:递归法

思路讲解: 将问题分解为:交换前两个节点,然后递归处理剩下的链表。递归返回的是「已处理完的链表头」,将其接到第一个节点(交换后变成了第二个节点)的后面。

关键洞察: 递归法的终止条件是空或只有一个节点。每次递归需要返回新的一对的「头节点」(即原来的第二个节点)。

def swapPairs(head):
    """递归法"""
    # 终止条件:空或只有一个节点
    if not head or not head.next:
        return head
    
    # 保存要交换的两个节点
    first = head
    second = head.next
    
    # 交换:first 指向递归返回的结果
    first.next = swapPairs(second.next)
    second.next = first
    
    # 返回新的头节点(原第二个节点)
    return second

易错点

  • 三步交换的顺序: 必须按照 pre→p2 → p1→next → p2→p1 的顺序,不能乱。修改 pre.next 之前必须保证 p1、p2 已正确赋值
  • pre 的移动: 交换后 pre 移到 p1(原第一个节点,现在是第二对的前驱),而不是移到 p2
  • 循环条件: while pre.next and pre.next.next 检查是否有两个可交换的节点
  • 奇数个节点: 最后一个节点不参与交换,自然保留在末尾
  • 图解辅助: 强烈建议画图理解指针变化,链表指针操作光靠脑子想很容易出错

框架提炼

相邻交换三步模板: 适用于需要成对交换链表节点的场景。

# 在 pre 后交换 p1 和 p2 两个节点
p1 = pre.next
p2 = p1.next
pre.next = p2       # 前驱指向 p2
p1.next = p2.next   # p1 指向后续
p2.next = p1        # p2 指向 p1
pre = p1            # 前进到下一对

这个模板可以看作是 25 题「K 个一组翻转链表」在 K=2 时的特化实现。


关联题目