链表-----

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

<<Git>><<MySQL>>

🌟心向往之行必能至

题目回顾

LeetCode 25. K 个一组翻转链表

给定一个链表的头节点 head 和一个正整数 k,每 k 个节点一组进行翻转,返回修改后的链表。如果节点总数不是 k 的整数倍,请将最后剩余的节点保持原有顺序。

示例

  • 输入:head = [1,2,3,4,5], k = 2 → 输出:[2,1,4,3,5]
  • 输入:head = [1,2,3,4,5], k = 3 → 输出:[3,2,1,4,5]

要求

  • 不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
  • 空间复杂度要求为 O(1)O(1) 。

🐛 遇到的"坑":一段看似正确的代码

在尝试解题时,我写下了如下代码(已简化):

cpp

编辑

复制代码
1class Solution {
2public:
3    ListNode* reverseKGroup(ListNode* head, int k) {
4        // ... 计算长度 n ...
5        
6        // ❌ 错误点 1:拷贝构造函数陷阱
7        ListNode dummy = ListNode(0, head); 
8        ListNode* p0 = &dummy;
9        ListNode* pre = nullptr;
10        ListNode* cur = head;
11
12        while (n >= k) {
13            n -= k;
14            // ❌ 错误点 2:pre 未重置
15            for (int i = 0; i < k; ++i) {
16                ListNode* nxt = cur->next;
17                cur->next = pre;
18                pre = cur;
19                cur = nxt;
20            }
21            // ... 连接逻辑 ...
22            p0 = p0->next; 
23        }
24        return dummy.next;
25    }
26};

这段代码在逻辑上似乎很通顺:先算长度,再分组翻转,最后拼接。但在实际提交时,却遇到了两个严重问题。

💥 错误一:被删除的拷贝构造函数

代码行:

cpp

编辑

复制代码
1ListNode dummy = ListNode(0, head);

在 LeetCode 的环境中,ListNode 结构体通常被定义为不可拷贝(Deleted Copy Constructor),以防止浅拷贝带来的指针混乱。因此,这行代码会直接报 Compile Error

✅ 修正方案

直接使用构造函数初始化,避免赋值拷贝:

cpp

编辑

复制代码
1ListNode dummy(0, head); // 正确:直接在栈上构造

💥 错误二:致命的指针逻辑漏洞

更隐蔽的错误在于变量 pre 的使用。

在第一次循环(第一组翻转)结束后,pre 指向了第一组的新头节点。

进入第二次循环(第二组翻转)时,pre 没有被重置为 nullptr

后果推演

当翻转第二组的第一个节点时,执行 cur->next = pre。此时 pre 还是第一组的尾节点(即原第一组的头)。

这导致:第二组的第一个节点指回了第一组,链表瞬间成环或顺序大乱!

✅ 修正方案

必须在每组翻转开始前,将 pre 重置为 nullptr


💡 核心解法:迭代 + 哨兵节点

修复上述错误后,我们得到一个标准的迭代解法 。其核心思想是:利用哨兵节点简化头节点处理,分组进行局部翻转,再重新连接

算法步骤详解

  1. 统计长度 :遍历链表获取总长度 n,用于判断剩余节点是否足够 k 个。
  2. 引入哨兵 :创建 dummy 节点指向 head,设 p0 指向 dummyp0 始终指向待翻转组的前一个节点)。
  3. 循环翻转
    • 检查剩余长度 n >= k
    • 重置指针pre = nullptr, cur = p0->next
    • 局部翻转 :执行 k 次标准链表翻转操作(cur->next = pre...)。
    • 重新连接
      • 记录当前组的旧头节点(翻转后变为尾):tail = p0->next
      • p0->next = pre:前驱连上新头。
      • tail->next = cur:旧头(现尾)连上后继节点。
    • 移动游标p0 = tail,准备处理下一组。
  4. 返回结果dummy.next

✅ 最终 AC 代码

cpp

编辑

复制代码
1class Solution {
2public:
3    ListNode* reverseKGroup(ListNode* head, int k) {
4        if (!head || k == 1) return head;
5
6        // 1. 计算链表总长度
7        ListNode* cur = head;
8        int n = 0;
9        while (cur) {
10            cur = cur->next;
11            ++n;
12        }
13
14        // 2. 初始化哨兵节点 (注意:直接构造,不要拷贝)
15        ListNode dummy(0, head);
16        ListNode* p0 = &dummy;
17        
18        // 重置 cur 指向真正的头节点
19        cur = head;
20        
21        while (n >= k) {
22            n -= k;
23            
24            // 【关键修复】每组翻转前,pre 必须重置为 nullptr
25            ListNode* pre = nullptr; 
26            
27            // 3. 局部翻转 k 个节点
28            for (int i = 0; i < k; ++i) {
29                ListNode* nxt = cur->next;
30                cur->next = pre;
31                pre = cur;
32                cur = nxt;
33            }
34
35            // 4. 重新连接链表
36            // p0 -> [旧头...旧尾] -> cur
37            // 翻转后:p0 -> [新头(pre)...新尾(旧头)] -> cur
38            
39            ListNode* tail = p0->next; // 记录当前的尾节点(即翻转前的头)
40            
41            p0->next = pre;      // 上一组尾 -> 当前组新头
42            tail->next = cur;    // 当前组尾 -> 下一组头
43            
44            p0 = tail;           // 更新 p0 为当前组的尾,准备处理下一组
45        }
46
47        return dummy.next;
48    }
49};

📊 复杂度分析

  • 时间复杂度 : O(N)O(N)
    • 第一次遍历计算长度耗时 O(N)O(N) 。
    • 后续每组翻转,每个节点仅被访问和反转一次,总耗时 O(N)O(N) 。
    • 整体为线性时间。
  • 空间复杂度 : O(1)O(1)
    • 仅使用了 dummy, p0, cur, pre, tail 等常数个指针变量。
    • 没有使用递归栈或额外数组,满足进阶要求。

🧠 总结与思考

这道题是链表操作的集大成者,它考察了以下几个关键点:

  1. C++ 基础细节:对象构造与拷贝控制(Rule of Three/Five),在刷题时也不能忽视语言特性。
  2. 指针状态管理 :在循环中,临时变量(如 pre)的状态是否需要在每轮重置?这是很多 Bug 的根源。
  3. 哨兵节点技巧dummy 节点能极大简化对头节点的特殊处理,使代码逻辑更统一。
  4. 分组处理模式while(n>=k) 配合内部 for 循环,是处理"每 K 个操作一次"类问题的通用模板。

避坑指南

  • 看到 ListNode 定义,下意识避免拷贝构造。
  • 写链表翻转循环时,时刻问自己:"这个指针在下一轮循环开始时,应该是多少?"
相关推荐
一叶落4382 小时前
167. 两数之和 II - 输入有序数组【C语言题解】
c语言·数据结构·算法·leetcode
地平线开发者2 小时前
征程6 MCU safetylib sample
算法·自动驾驶
Barkamin2 小时前
归并排序的简单实现
数据结构
小范自学编程2 小时前
算法训练营 Day37 - 动态规划part06
算法·动态规划
星空露珠2 小时前
迷你世界UGC3.0脚本Wiki角色模块管理接口 Actor
开发语言·数据库·算法·游戏·lua
我星期八休息2 小时前
深入理解哈希表
开发语言·数据结构·c++·算法·哈希算法·散列表
一叶落4382 小时前
LeetCode 54. 螺旋矩阵(C语言详解)——模拟 + 四边界收缩
java·c语言·数据结构·算法·leetcode·矩阵
优思学苑3 小时前
优思学院:QC新七大手法之「矩阵图法」是?
线性代数·矩阵
寻寻觅觅☆3 小时前
东华OJ-进阶题-19-排队打水问题(C++)
开发语言·c++·算法