【力扣100题】17.K 个一组翻转链表

1. 题目描述

给你链表的头节点 head,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例

复制代码
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
解释:每2个节点一组翻转:1和2翻转,3和4翻转,5保留

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
解释:每3个节点一组翻转:1、2、3翻转,4和5保留

输入:head = [1,2,3], k = 1
输出:[1,2,3]
解释:每1个节点一组,等于不翻转

提示

复制代码
链表中的节点数目为 n
1 <= k <= n <= 5000
0 <= Node.val <= 1000

进阶

你能设计一个只用 O(1) 额外内存空间的算法解决此问题吗?


2. 核心思想

关键思想:模拟 + 翻转 + 组装

核心思路:先判断链表能分成几组,然后对每一组进行翻转,最后将各组连接起来。

这是第 24 题「两两交换链表中的节点」的进阶版------从每组 2 个扩展到每组 k 个。

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

第1步:先统计链表长度 n = 5

第2步:翻转第1组(节点1和2)
  原始:1 → 2 → 3 → 4 → 5
  翻转:2 → 1 → 3 → 4 → 5
  此时:h = dummy, pre = 2, cur = 3

第3步:翻转第2组(节点3和4)
  原始:3 → 4 → 5
  翻转:4 → 3 → 5
  连接:2 → 1 → 4 → 3 → 5

第4步:剩余1个节点,不翻转

最终:2 → 1 → 4 → 3 → 5 ✓

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* reverseKGroup(ListNode* head, int k) {
        int n = 0;
        ListNode* dummy = head;
        
        // 第一步:统计链表长度
        while (dummy) {
            n++;
            dummy = dummy->next;
        }
        
        // 第二步:创建虚拟头节点
        dummy = new ListNode(0, head);
        ListNode* h = dummy;       // h 指向当前组的前一个节点
        ListNode* pre = NULL;      // 翻转指针
        ListNode* cur = head;      // 当前待处理节点
        
        // 第三步:每 k 个节点一组进行翻转
        for (; n >= k; n -= k) {
            // 翻转当前 k 个节点
            for (int i = 0; i < k; i++) {
                ListNode* nxt = cur->next;
                cur->next = pre;
                pre = cur;
                cur = nxt;
            }
            
            // 保存下一组的起始节点
            ListNode* nxt = h->next;
            
            // 连接:当前组翻转后的尾节点指向下一组
            h->next->next = cur;
            
            // 连接:当前组的前一个节点指向翻转后的头
            h->next = pre;
            
            // h 移动到下一组的"前一个节点"位置
            h = nxt;
        }
        
        return dummy->next;
    }
};

复杂度: 时间 O(n),空间 O(1) ✅(满足进阶要求)


方法二:递归法

cpp 复制代码
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        // 统计当前组是否有 k 个节点
        int count = 0;
        ListNode* cur = head;
        while (cur && count < k) {
            count++;
            cur = cur->next;
        }
        
        // 不足 k 个,不翻转
        if (count < k) {
            return head;
        }
        
        // 翻转当前 k 个节点
        ListNode* pre = NULL;
        cur = head;
        for (int i = 0; i < k; i++) {
            ListNode* nxt = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nxt;
        }
        
        // 递归处理后面的链表
        head->next = reverseKGroup(cur, k);
        
        return pre;  // pre 是翻转后的头节点
    }
};

复杂度: 时间 O(n),空间 O(n)(递归栈)❌(不满足进阶要求)


4. 图解过程

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

复制代码
初始状态:
dummy(0) → 1 → 2 → 3 → 4 → 5 → nullptr
 ↑
  h, pre=NULL, cur=1, n=5

翻转第1组(k=2):
  翻转后:pre=2→1→NULL, cur=3
  nxt = h->next = 1
  
  h->next->next = cur  → 1→3
  h->next = pre        → dummy→2
  
  h = nxt = 1

翻转第2组(k=2):
  翻转后:pre=4→3→NULL, cur=5
  nxt = h->next = 3
  
  h->next->next = cur  → 3→5
  h->next = pre        → 1→4
  
  h = nxt = 3

退出(n=1 < k=2)

最终:2 → 1 → 4 → 3 → 5 → nullptr ✓

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

复制代码
初始:n=5

翻转第1组(k=3):
  翻转后:pre=3→2→1→NULL, cur=4
  nxt = h->next = 1
  h->next->next = cur  → 1→4
  h->next = pre         → dummy→3
  h = nxt = 1

剩余 n=2 < k=3,不翻转

最终:3 → 2 → 1 → 4 → 5 → nullptr ✓

5. 方法优缺点比较

方法 时间 空间 满足进阶 缺点
迭代法 O(n) ✅ O(1) ✅ 代码较复杂
递归法 O(n) ✅ O(n) ❌ 递归栈开销,有栈溢出风险

推荐方法

迭代法 满足进阶要求,是面试和竞赛中的标准解法。


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* reverseKGroup(ListNode* head, int k) {
        int n = 0;
        ListNode* dummy = head;
        
        while (dummy) {
            n++;
            dummy = dummy->next;
        }
        
        dummy = new ListNode(0, head);
        ListNode* h = dummy;
        ListNode* pre = NULL;
        ListNode* cur = head;
        
        for (; n >= k; n -= k) {
            for (int i = 0; i < k; i++) {
                ListNode* nxt = cur->next;
                cur->next = pre;
                pre = cur;
                cur = nxt;
            }
            
            ListNode* nxt = h->next;
            h->next->next = cur;
            h->next = pre;
            h = nxt;
        }
        
        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,5], k=2 → [2,1,4,3,5]
    ListNode* h1 = toList({1, 2, 3, 4, 5});
    vector<int> r1 = toVector(sol.reverseKGroup(h1, 2));
    cout << "[1,2,3,4,5], k=2 → [";
    for (int i = 0; i < r1.size(); i++)
        cout << r1[i] << (i < r1.size()-1 ? "," : "");
    cout << "]" << endl;

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

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

    return 0;
}

输出:

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

7. 三道题对比总结

题目 核心技巧 关键点
删除倒数第n个 快慢指针差 n 步 slow 停在目标前一个节点
两两交换 虚拟头 + 逐对交换 pre 停在已交换部分末尾
K个一组翻转 计数 + 翻转 + 组装 n 记录剩余可翻转组数

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

相关推荐
洛水水5 小时前
【力扣100题】16.两两交换链表中的节点
算法·leetcode·链表
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)——直接选择排序、堆排序
数据结构·算法·排序算法