LeetCode 25. K 个一组翻转链表 reverse N 核心思路
LeetCode 25 题 K 个一组翻转链表。核心思路是将大问题拆解为 reverse N 计数判断与私有翻转两个基础能力。使用虚拟头节点解决头节点变化边界问题,通过双指针记录关键位置保证分组拼接不断裂。时间复杂度 O(n),空间复杂度 O(k)。代码提供 C++ 递归实现及调试踩坑复盘。

LeetCode 25 题 K 个一组翻转链表。核心思路是将大问题拆解为 reverse N 计数判断与私有翻转两个基础能力。使用虚拟头节点解决头节点变化边界问题,通过双指针记录关键位置保证分组拼接不断裂。时间复杂度 O(n),空间复杂度 O(k)。代码提供 C++ 递归实现及调试踩坑复盘。

以 k 个节点为一个分组,对链表的每一组进行完整反转;若链表剩余节点数量不足 k 个,则保持原有顺序不翻转。
输入链表:1 -> 2 -> 3 -> 4 -> 5,k = 3
1、2、3(满足 3 个节点)→ 翻转 → 3 -> 2 -> 14、5(不足 3 个节点)→ 不翻转3 -> 2 -> 1 -> 4 -> 5原链表:1->2->3->4->5 分组 k=3 第一组:1,2,3 第二组:4,5 翻转后:3->2->1 不翻转:4->5 最终链表:3->2->1->4->5 图表说明:清晰展示了「分组→判断长度→分组翻转→拼接结果」的全流程,不足 k 个的分组直接保留原序。
本题的核心解题思路是拆分法:将「K 组翻转」拆解为两个基础能力的组合——
作用:前置校验,避免对不足 k 个节点的分组执行翻转。 实现逻辑:
这是真正执行翻转的私有函数,严格遵循题目指定逻辑:
链表翻转的最大坑点是头节点会变化,因此必须使用虚拟头节点技巧破局;同时通过双指针记录关键位置,保证分组拼接不断裂。
创建一个虚拟节点 dummy,让 dummy->next = 原链表头节点。 作用:无论链表头节点如何翻转,最终直接返回 dummy->next 即可,彻底解决头节点边界问题。
我们定义两个核心指针,这是循环翻转的关键:
| 指针名称 | 核心作用 | 位置变化 |
|---|---|---|
| p | 待翻转区域的前驱节点 | 始终指向当前分组的前一个节点,初始指向虚拟头 |
| q | 待翻转区域的头节点 / 翻转后的尾节点 | 初始 = p->next,翻转后成为当前分组的尾节点,是下一次的 p |
以下是精简核心版代码,仅保留性能关键逻辑,注释全覆盖核心步骤:
#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,结合题目要求复盘调试过程:
问题:最初直接返回 head(原链表头节点),导致结果错乱; 原因:分组翻转后,原链表头节点变成了第一组的尾节点,不再是整个链表的头。
返回虚拟头节点的 next:return dummy->next,无论头节点如何翻转,虚拟头始终指向链表真正的头。
题目指定关键判断:
if (p->next != q) → 说明发生了翻转;
if (p->next == q) → 说明未翻转(不足 k 个节点)。
该判断用于验证分组是否成功翻转,是调试的核心依据。
这道题是链表递归 + 迭代的综合运用,吃透「reverse N」思路,所有链表分段翻转类题目都能迎刃而解!

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online