【力扣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 停在已交换部分末尾

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

相关推荐
wuweijianlove5 小时前
算法教学中的抽象建模与动态可视化设计的技术7
算法
2zcode5 小时前
基于改进YOLO11算法的芯片微缺陷检测系统(UI界面+数据集+分析界面+处置建议+训练代码)
算法·芯片缺陷
leoufung5 小时前
LeetCode 30:Substring with Concatenation of All Words 题解(含 C 语言 uthash 实现)
c语言·leetcode·c#
王老师青少年编程5 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:荷马史诗
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·荷马史诗
样例过了就是过了5 小时前
LeetCode热题100 最小路径和
c++·算法·leetcode·动态规划
Aaron15885 小时前
RFSOC+VU13P+GPU 在6G互联网中的技术应用
大数据·人工智能·算法·fpga开发·硬件工程·信息与通信·信号处理
迷途之人不知返6 小时前
Stack & Queue
c++·算法
没文化的阿浩6 小时前
【数据结构】排序(2)——直接选择排序、堆排序
数据结构·算法·排序算法
ytttr8736 小时前
基于libusb的用户空间UVC相机库
算法