【力扣100题】16.两两交换链表中的节点

1. 题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。

你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例

复制代码
输入:head = [1,2,3,4]
输出:[2,1,4,3]
解释:两两交换相邻节点,1和2交换,3和4交换

输入:head = [1,2,3,4,5]
输出:[2,1,4,3,5]
解释:两两交换,多的那个节点不动

输入:head = []
输出:[]
解释:空链表

输入:head = [1]
输出:[1]
解释:只有一个节点,无法交换

提示

复制代码
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100

2. 核心思想

关键思想:虚拟头节点 + 逐对交换

核心思路:每次找到要交换的两个节点,将它们反转,然后继续处理后面的节点。

使用虚拟头节点统一处理,因为头节点也可能被交换。

复制代码
以 [1,2,3,4] 为例:

初始:dummy → 1 → 2 → 3 → 4 → nullptr
         ↑    ↑
        node0 node1

交换 1 和 2:
步骤1:node0->next = node2(dummy指向2)
步骤2:node2->next = node1(2指向1)
步骤3:node1->next = node3(1指向3)

此时:dummy → 2 → 1 → 3 → 4 → nullptr
                 ↑   ↑
              node0 node1

移动 node0 = node1,node1 = node3,继续处理

3. 多种方法解决

方法一:四指针法(推荐)✅

cpp 复制代码
/**
 * Definition for singly-linked list.
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(0, head);  // 虚拟头节点
        ListNode* node0 = dummy;
        ListNode* node1 = head;
        
        while (node1 && node1->next) {
            ListNode* node2 = node1->next;      // 第二个节点
            ListNode* node3 = node2->next;       // 下一对的头
            
            node0->next = node2;                 // ① node0 指向 node2
            node2->next = node1;                 // ② node2 指向 node1
            node1->next = node3;                  // ③ node1 指向 node3
            
            node0 = node1;                        // node0 移动到已交换部分末尾
            node1 = node3;                       // node1 移动到下一对
        }
        
        return dummy->next;
    }
};

复杂度: 时间 O(L),空间 O(1),只遍历一次 ✅


方法二:三指针法

cpp 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* pre = dummy;
        
        while (pre->next && pre->next->next) {
            ListNode* cur = pre->next;
            ListNode* nxt = cur->next;
            
            cur->next = nxt->next;   // ① cur 指向 nxt 后面
            nxt->next = cur;         // ② nxt 指向 cur
            pre->next = nxt;         // ③ pre 指向 nxt
            
            pre = cur;               // pre 移动到已交换部分末尾
        }
        
        return dummy->next;
    }
};

复杂度: 时间 O(L),空间 O(1),与方法一本质相同 ✅


方法三:递归法(优雅)

cpp 复制代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        // 递归终止条件:空或只有一个节点
        if (!head || !head->next) {
            return head;
        }
        
        // 保存节点
        ListNode* first = head;
        ListNode* second = head->next;
        
        // 递归处理后面的链表
        ListNode* remaining = swapPairs(second->next);
        
        // 交换两个节点
        first->next = remaining;   // 第一个指向后面的交换结果
        second->next = first;       // 第二个指向第一个
        
        return second;              // 新的头节点是原来的第二个
    }
};

复杂度: 时间 O(L),空间 O(L)(递归栈)✅


4. 图解过程

示例:head = [1,2,3,4]

复制代码
初始状态:
dummy(0) → 1 → 2 → 3 → 4 → nullptr
 ↑
node0=dummy, node1=1

第1轮交换(1和2):
  node2=2, node3=3
  node0->next = node2   → dummy→2
  node2->next = node1   → 2→1
  node1->next = node3   → 1→3
  
  更新:node0=1, node1=3

第2轮交换(3和4):
  node2=4, node3=nullptr
  node0->next = node2   → 1→4
  node2->next = node1   → 4→3
  node1->next = node3   → 3→nullptr
  
  更新:node0=3, node1=nullptr

退出!最终:2→1→4→3→nullptr ✓

边界情况:head = [1,2,3]

复制代码
dummy → 1 → 2 → 3 → nullptr

第1轮交换(1和2):
  结果:dummy → 2 → 1 → 3 → nullptr

第2轮:
  node0 = 1
  node1 = 3
  node1->next = nullptr,退出

最终:2 → 1 → 3 ✓
(多余的节点3保留)

5. 方法优缺点比较

方法 时间 空间 优点 缺点
四指针法 O(L) ✅ O(1) ✅ 直观,命名清晰 代码稍长
三指针法 O(L) ✅ O(1) ✅ 简洁 pre/cur/nxt 命名需要理解
递归法 O(L) ✅ O(L) ❌ 代码极简优雅 递归栈开销,有栈溢出风险

推荐方法

四指针法(方法一)三指针法(方法二) 是面试和竞赛中的标准解法:

  1. 一趟扫描,时间最优
  2. O(1) 空间
  3. 掌握后代码很简洁

6. 完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

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) {}
};

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* node0 = dummy;
        ListNode* node1 = head;
        
        while (node1 && node1->next) {
            ListNode* node2 = node1->next;
            ListNode* node3 = node2->next;
            
            node0->next = node2;
            node2->next = node1;
            node1->next = node3;
            
            node0 = node1;
            node1 = node3;
        }
        
        return dummy->next;
    }
};

// 辅助函数:链表转 vector(方便打印)
vector<int> toVector(ListNode* head) {
    vector<int> result;
    while (head) {
        result.push_back(head->val);
        head = head->next;
    }
    return result;
}

// 辅助函数:vector 转链表
ListNode* toList(vector<int> vals) {
    ListNode* dummy = new ListNode(0);
    ListNode* curr = dummy;
    for (int v : vals) {
        curr->next = new ListNode(v);
        curr = curr->next;
    }
    return dummy->next;
}

int main() {
    Solution sol;

    // 测试1:[1,2,3,4] → [2,1,4,3]
    ListNode* h1 = toList({1, 2, 3, 4});
    vector<int> r1 = toVector(sol.swapPairs(h1));
    cout << "[1,2,3,4] → [";
    for (int i = 0; i < r1.size(); i++)
        cout << r1[i] << (i < r1.size()-1 ? "," : "");
    cout << "]" << endl;

    // 测试2:[1,2,3] → [2,1,3]
    ListNode* h2 = toList({1, 2, 3});
    vector<int> r2 = toVector(sol.swapPairs(h2));
    cout << "[1,2,3] → [";
    for (int i = 0; i < r2.size(); i++)
        cout << r2[i] << (i < r2.size()-1 ? "," : "");
    cout << "]" << endl;

    // 测试3:[] → []
    ListNode* h3 = toList({});
    vector<int> r3 = toVector(sol.swapPairs(h3));
    cout << "[] → []" << endl;

    // 测试4:[1] → [1]
    ListNode* h4 = toList({1});
    vector<int> r4 = toVector(sol.swapPairs(h4));
    cout << "[1] → [";
    for (int i = 0; i < r4.size(); i++)
        cout << r4[i] << (i < r4.size()-1 ? "," : "");
    cout << "]" << endl;

    return 0;
}

输出:

复制代码
[1,2,3,4] → [2,1,4,3]
[1,2,3] → [2,1,3]
[] → []
[1] → [1]

7. 两道题对比总结

题目 核心技巧 关键点
删除倒数第n个 快慢指针差 n 步 slow 停在目标前一个节点
两两交换 虚拟头 + 逐对交换 pre/node0 停在已交换部分末尾

两题的共同套路:虚拟头节点统一处理头节点 + 画图理解指针变化 🔄

相关推荐
心中有国也有家28 分钟前
hccl 架构拆解:昇腾集合通信库到底在做什么?
人工智能·经验分享·笔记·分布式·算法·架构
小O的算法实验室1 小时前
2026年MCS,Q-learning增强MOPSO与改进DWA融合算法+复杂三维地形下特定移动机器人动态路径规划
算法
Peter·Pan爱编程2 小时前
10. new_delete 不是 malloc_free 的包装
c++·人工智能·算法
故事和你913 小时前
洛谷-【动态规划1】动态规划的引入2
开发语言·数据结构·c++·算法·动态规划·图论
重生之我是Java开发战士3 小时前
【动态规划】背包问题:完全背包,二位费用的背包问题,似包非包
算法·动态规划
LabVIEW开发4 小时前
LabVIEW实现FDTD 电磁仿真
算法·labview·labview知识·labview功能·labview程序
Together_CZ4 小时前
DTSemNet :Vanilla Gradient Descent for Oblique Decision Trees——用于倾斜决策树的普通梯度下降
算法·决策树·机器学习·vanilla·gradient·dtsemnet·用于倾斜决策树的普通梯度
一条大祥脚4 小时前
ABC459 贪心构造|树形DP|组合数学|贪心|单调栈|势能|前缀和
算法·深度优先
灰灰勇闯IT5 小时前
DeepEP:MoE 推理的 AllToAll 通信瓶颈怎么解
算法·cann
一行代码一行诗++5 小时前
goto语句
java·开发语言·算法