LeetCode 25. K 个一组翻转链表 | 硬核拆解「reverse N」核心思路

LeetCode 25. K 个一组翻转链表 | 硬核拆解「reverse N」核心思路

✨ LeetCode 25. K 个一组翻转链表 | 硬核拆解「reverse N」核心思路

链表作为数据结构的基础考点,K 个一组翻转链表是进阶高频题型,它完美融合了「单组链表翻转」与「分段处理」的核心思想。本文将严格围绕reverse N 实现思路,从问题定义、算法原理、代码实现到调试踩坑,逐帧拆解这道经典题,带你彻底吃透链表分段翻转的底层逻辑!


视频地址

因为想更好的为大佬服务,制作了同步视频,这是Bilibili的视频地址

🔍 一、问题核心含义:K 个一组翻转链表

1. 官方定义(严格版)

k 个节点为一个分组,对链表的每一组进行完整反转;若链表剩余节点数量不足 k 个,则保持原有顺序不翻转

2. 直观示例

输入链表:1 -> 2 -> 3 -> 4 -> 5,k = 3

  • 第一组:1、2、3(满足 3 个节点)→ 翻转 → 3 -> 2 -> 1
  • 第二组:4、5(不足 3 个节点)→ 不翻转
  • 最终输出:3 -> 2 -> 1 -> 4 -> 5

3. 可视化流程图(Mermaid)

原链表: 1->2->3->4->5

分组 k=3

第一组: 1,2,3

第二组: 4,5

翻转后: 3->2->1

不翻转: 4->5

最终链表: 3->2->1->4->5

图表说明:清晰展示了「分组→判断长度→分组翻转→拼接结果」的全流程,不足 k 个的分组直接保留原序。


📊 二、算法原理:基于「reverse N」的分层实现

本题的核心解题思路是拆分法:将「K 组翻转」拆解为两个基础能力的组合——

  1. reverse N 计数判断:检查当前链表是否有足够的 N 个节点供翻转;
  2. __reverse N 私有翻转:真正实现「翻转链表前 N 个节点」的核心逻辑。

这是官方推荐的最优解思路,无冗余操作,时间复杂度趋近于 O(n)。

1. 基础能力:reverse N 节点计数判断

作用:前置校验,避免对不足 k 个节点的分组执行翻转。

实现逻辑:

  • 定义指针 p 指向当前分组的头节点;
  • 循环递减 k,同时让 p 向后遍历;
  • 若循环结束后 p 不为空 → 剩余节点 ≥k,可翻转;
  • p 为空 → 剩余节点 <k,终止翻转。

2. 核心能力:__reverse N 前 N 个节点翻转

这是真正执行翻转的私有函数,严格遵循题目指定逻辑:

  • 递归终止条件:n == 1,直接返回当前头节点(最后一个节点是新头);
  • 定义 tail:指向 head->next(原分组头节点,翻转后成为分组尾节点);
  • 定义 newHead:递归翻转后的新头节点;
  • 最终返回 newHead(翻转后分组的头节点)。

💡 三、整体实现框架:虚拟头节点 + 双指针循环

链表翻转的最大坑点是头节点会变化,因此必须使用虚拟头节点技巧破局;同时通过双指针记录关键位置,保证分组拼接不断裂。

1. 核心技巧:虚拟头节点

创建一个虚拟节点 dummy,让 dummy->next = 原链表头节点

作用:无论链表头节点如何翻转,最终直接返回 dummy->next 即可,彻底解决头节点边界问题

2. 关键位置指针定义

我们定义两个核心指针,这是循环翻转的关键:

指针名称核心作用位置变化
p待翻转区域的前驱节点始终指向当前分组的前一个节点,初始指向虚拟头
q待翻转区域的头节点 / 翻转后的尾节点初始 = p->next,翻转后成为当前分组的尾节点,是下一次的 p

3. 循环翻转逻辑

  1. reverseN(q, k) 为循环条件(返回非空说明可翻转);
  2. 执行翻转,将前驱节点 p 与新头节点拼接;
  3. 指针后移:p = q(当前分组尾 → 下一组前驱),q = p->next(下一组头);
  4. 直到 reverseN 返回空,循环结束。

⚠️ 四、C++ 关键代码实现

以下是精简核心版代码,仅保留性能关键逻辑,注释全覆盖核心步骤:

 #include <iostream> 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: // ===================== 核心1:翻转前n个节点(__reverseN)===================== ListNode* __reverseN(ListNode* head, int n) { // 终止条件:n=1,返回当前头(翻转后新头) if (n == 1) return head; // tail:原分组头,翻转后是分组尾节点 ListNode* tail = head->next; // 递归翻转剩余n-1个节点 ListNode* newHead = __reverseN(head->next, n-1); // 拼接节点:尾节点指向当前头 tail->next = head; head->next = nullptr; return newHead; } // ===================== 核心2:判断是否有n个节点(reverseN判断)===================== ListNode* hasNNode(ListNode* head, int n) { ListNode* p = head; // 向后数n个节点 while (n-- && p) { p = p->next; } // p非空=有n个节点,返回p;空=不足n个,返回nullptr return p; } // ===================== 主函数:K个一组翻转 ===================== ListNode* reverseKGroup(ListNode* head, int k) { // 虚拟头节点(解决头节点翻转边界) ListNode* dummy = new ListNode(-1); dummy->next = head; // p:待翻转区前驱;q:待翻转区头节点 ListNode* p = dummy; ListNode* q = p->next; // 循环:有k个节点则翻转,否则终止 while (hasNNode(q, k)) { // 翻转q开头的k个节点,得到新头 ListNode* newHead = __reverseN(q, k); // 前驱节点拼接新头 p->next = newHead; // 指针后移:p=当前分组尾,q=下一组头 p = q; q = p->next; } // 返回最终链表头(虚拟头的next) return dummy->next; } }; 

🐛 五、调试实战:踩坑与修复(返回值核心问题)

在编写代码时,返回值错误是最常见的bug,结合题目要求复盘调试过程:

1. 初始错误

✅ 问题:最初直接返回 head(原链表头节点),导致结果错乱;

✅ 原因:分组翻转后,原链表头节点变成了第一组的尾节点,不再是整个链表的头。

2. 修复方案

返回虚拟头节点的nextreturn dummy->next,无论头节点如何翻转,虚拟头始终指向链表真正的头。

3. 翻转判断逻辑

题目指定关键判断:

if (p->next != q) → 说明发生了翻转;

if (p->next == q) → 说明未翻转(不足k个节点)。

该判断用于验证分组是否成功翻转,是调试的核心依据。


📈 六、复杂度分析

  1. 时间复杂度:O(n)

每个节点仅被遍历一次(计数一次 + 翻转一次),无嵌套循环;

  1. 空间复杂度:O(k)

递归调用栈深度为 k(分组长度),若改用迭代版 __reverseN,空间可优化为 O(1)。


✅ 七、总结与技巧提炼

  1. 核心思想:大问题拆小 → 用 reverseN 做计数校验,__reverseN 做单组翻转;
  2. 必用技巧:虚拟头节点解决链表头变化的边界问题;
  3. 指针关键p 记录前驱,q 记录分组头尾,保证链表不断裂;
  4. 终止条件:剩余节点不足 k 个时,直接终止循环,不做任何操作。
 LeetCode 25. K 个一组翻转链表 | 硬核拆解「reverse N」核心思路

这道题是链表递归+迭代的综合运用,吃透「reverse N」思路,所有链表分段翻转类题目都能迎刃而解!

Read more

《算法题讲解指南:优选算法-滑动窗口》--15.串联所有单词的子串,16.最小覆盖子串

《算法题讲解指南:优选算法-滑动窗口》--15.串联所有单词的子串,16.最小覆盖子串

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 15. 串联所有单词的子串 题目链接: 题目描述: 题目示例: 解法(滑动窗口+哈希表): 算法思路: C++算法代码: 算法总结及流程解析: 16. 最小覆盖子串 题目链接: 题目描述: 题目示例: 解法 (滑动窗口+哈希表): 算法思路: 算法流程: C++算法代码: 算法总结及流程解析: 结束语 15. 串联所有单词的子串 题目链接: 30. 串联所有单词的子串 - 力扣(LeetCode)

By Ne0inhk
Flutter 三方库 statistics 鸿蒙高性能数据回归科学系统全域适配:将顶尖数理统计算法与重负载大模型双栈引擎植入微距节点彻底盘活泛计算终端底层数据-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 statistics 鸿蒙高性能数据回归科学系统全域适配:将顶尖数理统计算法与重负载大模型双栈引擎植入微距节点彻底盘活泛计算终端底层数据-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 statistics 鸿蒙高性能数据回归科学系统全域适配:将顶尖数理统计算法与重负载大模型双栈引擎植入微距节点彻底盘活泛计算终端底层数据感知系统 前言 在鸿蒙生态的智慧医疗、金融理财及运动健康类应用中,实时对传感器数据或业务流水进行深度统计分析是核心能力。例如,通过运动步频计算方差以识别走跑状态,或根据心率波动进行回归分析以预测压力指数。statistics 库作为 Dart 生态中轻量且纯粹的数学工具集,为这类需求提供了高性能的底层支持。本文将探讨如何在 OpenHarmony 上适配该库,实现设备侧的大数据即时运算。 一、原理解析 / 概念介绍 1.1 基础原理/概念介绍 statistics 库不依赖外部厚重的二进制 C++ 库,它通过 Dart 语言级优化实现了对 Iterable<num> 的原生扩展。其核心逻辑聚焦于描述性统计(Descriptive Statistics)与回归模型(Regression

By Ne0inhk
【贪心算法】day9

【贪心算法】day9

📝前言说明: * 本专栏主要记录本人的贪心算法学习以及LeetCode刷题记录,按专题划分 * 每题主要记录:(1)本人解法 + 本人屎山代码;(2)优质解法 + 优质代码;(3)精益求精,更好的解法和独特的思想(如果有的话);(4)贪心策略正确性的 “证明” * 文章中的理解仅为个人理解。如有错误,感谢纠错 🎬个人简介:努力学习ing 📋本专栏:C++刷题专栏 📋其他专栏:C语言入门基础,python入门基础,C++学习笔记,Linux 🎀ZEEKLOG主页 愚润泽 你可以点击下方链接,进行其他贪心算法题目的学习 点击链接开始学习贪心day1贪心day2贪心day3贪心day4贪心day5贪心day6贪心day7贪心day8贪心day9贪心day10 也可以点击下面连接,学习其他算法 点击链接开始学习优选专题动态规划递归、搜索与回溯贪心算法 题单获取→ 【贪心算法】题单汇总 题目 * 452. 用最少数量的箭引爆气球 * 个人解 * 397. 整数替换 * 优质解

By Ne0inhk
从零开始打造高性能数据结构——手把手教你实现环形缓冲

从零开始打造高性能数据结构——手把手教你实现环形缓冲

◆ 博主名称: 小此方-ZEEKLOG博客 大家好,欢迎来到小此方的博客。 ⭐️个人专栏:《C语言》_小此方的博客-ZEEKLOG博客 算法_小此方的博客-ZEEKLOG博客  ⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰。 目录 一,普通队列的劣势 1. 空间浪费严重(“假溢出”问题) 2. 需要频繁移动元素(若避免浪费) 3. 扩容成本高 4. 无法解决“假溢出”导致的提前扩容 二,环形缓冲结构分析  1. “循环”取模实现指针回绕  2.“循环”,轮流入座而不是排长队 三,实现环形缓冲 1,MyCircularQueue(k): 构造器   1,结构体搭建   2,初始化 3,为什么选择k+1块空间而不是k块空间?

By Ne0inhk