【链表】LC 25.K 个一组翻转链表

文章目录

前言

本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。

一、题目

1、原题链接

25.K 个一组翻转链表

2、题目描述

二、个人思路整理

1、思路分析

核心思路:利用总长度控制循环,用头插法原地翻转:

  1. 统计总长:先遍历一遍链表求出总节点数 n。这样通过判断 n >= k 就能知道剩余节点够不够一组,省去了每次翻转前"先探路走 k k k 步"的麻烦。

  2. 定位四个核心指针:

    • dummy:哨兵节点,指向 head,用来统一处理头节点被翻转的情况。
    • p0:当前待翻转组的上一个节点(即上一组的尾巴)。初始指向 dummy。
    • pre 和 cur:标准的反转链表双指针,初始 pre = nullptr,cur = p0->next。
  3. 组内反转与衔接:

    • 在组内循环 k k k 次,执行最基础的链表反转(tmp = cur->next; cur->next = pre; pre = cur; cur = tmp;)。
    • k k k 次循环结束后,pre 停在当前组的新头节点,cur 停在下一组的头节点,而 p0->next 依然指着当前组原本的头节点(现在变成了当前组的尾节点)。
    • 只需两步即可完成拼接与指针推进:
      • 让当前组的尾巴连上下一组的头:p0->next->next = cur;
      • 让上一组的尾巴连上当前组的新头:p0->next = pre;
      • 将 p0 移动到当前组的尾巴(即原先的 p0->next),准备处理下一组。

2、解题代码

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) {
        // 1. 统计链表总长度
        int n = 0;
        ListNode* tmp = head;
        while (tmp) {
            tmp = tmp->next;
            n++;
        }

        // 2. 初始化哨兵节点与指针
        ListNode dummy(0, head);
        ListNode* p0 = &dummy;    // p0 始终指向待翻转那一组的前驱节点
        ListNode* pre = nullptr;
        ListNode* cur = head;

        // 3. 只要剩余节点数 >= k,就继续翻转
        int tmpk = k;
        while (n >= k) {
            n -= k;
            tmpk = k;
            while (tmpk--) {
                // 标准反转链表逻辑,循环 k 次
                ListNode* tmp = cur->next;
                cur->next = pre;
                pre = cur;
                cur = tmp;
            }

            // 此时:
            // pre 是当前数组反转后的【新头】
            // cur 是下一组的【头】
            // p0->next 是当前组反转前的【旧头】(即反转后的【新尾】)
            ListNode* next_p0 = p0->next;      // 先暂存当前组的新尾,将它作为下一轮的 p0
            p0->next->next = cur;      // 当前组的新尾 -> 指向下一组的头
            p0->next = pre;            // 上一组的尾(p0)->指向当前组的新头
            p0 = next_p0;              // p0 推进到当前组的新尾
        }

        return dummy.next;
    }
};

复杂度分析

  • 时间复杂度: O ( N ) O(N) O(N),遍历两次,一次算长度,一次做翻转。
  • 空间复杂度: O ( 1 ) O(1) O(1),仅在栈上分配了一个 dummy 对象和几个指针。

三、知识风暴

链表分组翻转(Reverse Nodes in k-Group) 是本题的核心:题目要求把链表每 k k k 个节点视为一组,组内原地翻转,不足 k k k 个的尾部保持原样。它本质上是「基础链表反转」的升级版------把一次整体反转拆成若干次定长反转,并在组与组之间做好衔接。

算法核心思想:

  • 先统计总长,用长度控制循环 :先遍历一遍求出节点总数 n,之后每翻转一组就执行 n -= k,只要 n >= k 就继续。这样无需每次翻转前"探路走 k k k 步"判断剩余节点是否够一组,逻辑更简洁。
  • 哨兵节点统一边界 :引入 dummy 指向 head,让"头节点所在组被翻转"与"中间组被翻转"走同一套代码,避免单独处理头节点。
  • 组内头插式反转 :在每组内部用 pre / cur 双指针循环 k k k 次完成反转, k k k 次结束后 pre 是新头、cur 是下一组的头,而 p0->next 仍指向本组旧头(即反转后的新尾)。
  • 两步完成衔接 :p0->next->next = cur 让本组新尾接上下一组;p0->next = pre 让上一组尾巴接上本组新头;最后把 p0 推进到本组新尾,准备处理下一组。

常见对比:迭代原地翻转 vs 递归分组翻转

  • 迭代(本题解法) :用 dummy + p0 + pre / cur 在链表上原地操作,空间复杂度 O ( 1 ) O(1) O(1),是面试中最推荐的写法;但指针较多,衔接顺序写错容易断链,需要理清"新头 / 新尾 / 下一组头"三者的关系。
  • 递归分组翻转 :先判断剩余节点是否够 k k k 个,够则翻转前 k k k 个,再递归处理后续部分并把本组新尾指向递归结果。代码更短、更贴近分治直觉,但递归深度为 O ( n / k ) O(n/k) O(n/k),存在栈开销。
  • 借助数组 / 栈 :把每组节点值压入栈再弹出重写,思路直观、不易出错,但需要额外 O ( k ) O(k) O(k) 空间,且只改值不改节点,不符合"原地翻转节点"的题意要求。

指针衔接的关键细节:

  • 为什么先存 next_p0 :执行 p0->next = pre 之后,原来的 p0->next(本组新尾)就被覆盖了,所以必须先用 next_p0 暂存,再把它作为下一轮的 p0。
  • pre 的复位 :每进入新一组前,pre 需要重新置为 nullptr,否则会把上一组的节点错误地接到本组新头之后。
  • 不足 k k k 个的处理 :由于循环条件是 n >= k,剩余不足 k k k 个的节点根本不会进入翻转逻辑,天然保持原顺序,无需额外判断。

使用要点:

  • 长度先行 :先算 n 再翻转,是本题最省心的写法,务必牢记这个"用总长度控制循环"的套路。
  • 哨兵节点必备 :凡是涉及"可能翻转头节点"的链表题,dummy 都是降低边界复杂度的利器。
  • 画图验证指针 :组内反转 + 组间衔接共涉及 4 个指针,建议在纸上画出 dummy -> 组1 -> 组2 的示意图,逐步核对每一步的指向。
  • 返回 dummy.next :最终头节点可能已不是原 head,必须返回 dummy.next 而非 head。

算法变体与扩展:

  • 两两交换链表中的节点(LeetCode 24) :即 k = 2 k = 2 k=2 的特例,可用同样的分组翻转框架,也可用递归实现,适合作为本题的热身。
  • 反转链表(LeetCode 206) :本题组内反转的基础模板,理解 pre / cur 双指针反转是解决本题的前提。
  • 反转链表 II(LeetCode 92) :只翻转指定区间 l e f t , r i g h t left, right left,right,与本题"定长分组翻转"互为补充,可对比"区间边界定位"与"分组边界定位"的差异。
  • K 个一组翻转链表(递归写法):同一道题的递归版本,可与本题迭代写法对照,体会"迭代控指针、递归控结构"的取舍。

相关 LeetCode 例题:

相关推荐
careathers1 小时前
【数据结构】基于比较的排序算法 - 插入排序
java·数据结构·排序算法
MicrosoftCloud1 小时前
数据结构 07(上)|查找:顺序、折半、分块,ASL 到底怎么
数据结构·查找·顺序查找·折半查找·分块查找·平均查找长度·asl
Mortalbreeze2 小时前
0 - 1 BFS 算法详解:从基本原理到经典题目建模
数据结构·c++·算法·图论·宽度优先
shehuiyuelaiyuehao16 小时前
算法56,重排链表
数据结构·算法·链表
suhan17 小时前
【数据结构】七、查找:1.查找的概念、线性结构查找(顺序、折半(二分)、插值、稠密、分块、倒排)
数据结构·算法·排序算法
极客 - L U18 小时前
卡尔曼滤波从零到精通
c语言·数据结构·单片机·嵌入式硬件·mcu
Sarvartha20 小时前
栈的四则运算、栈的递归应用
数据结构·算法
YXXY31321 小时前
链表的相关介绍
链表
GG.WinGel1 天前
三元组中心问题
java·数据结构·算法·青少年编程·蓝桥杯