高效解决K个一组翻转链表的算法详解
1. 题目链接
LeetCode 25. K个一组翻转链表
2. 题目描述
给定一个链表和一个正整数k
,要求每k
个节点为一组进行翻转,剩余不足k
的节点保持原有顺序。例如:
- 输入:
1 → 2 → 3 → 4 → 5
,k=2
- 输出:
2 → 1 → 4 → 3 → 5
3. 示例分析
假设输入链表为1 → 2 → 3 → 4 → 5
,k=3
:
- 前3个节点
1→2→3
翻转得到3→2→1
- 后2个节点
4→5
不足k
,保持原顺序 - 最终结果:
3 → 2 → 1 → 4 → 5
可视化翻转过程:
初始状态:dummy → |1 → 2 → 3| → 4 → 5
第一次翻转:dummy → 3 → 2 → 1 → |4 → 5|
4. 算法思路
分组头插法(时间复杂度O(n))
核心思想:将链表划分为多个k
节点长度的子链表,利用头插法实现原地翻转。
具体步骤:
- 计算翻转次数:遍历链表获取总长度,计算需要翻转的组数
n = 总长度 / k
- 分组翻转:
- 使用哑节点
dummy
简化头节点处理 - 每次处理
k
个节点,通过头插法实现子链表翻转
- 使用哑节点
- 连接剩余节点:处理完所有完整组后,直接连接剩余节点
5. 边界条件与注意事项
- k=1的特殊情况:直接返回原链表
- 链表长度不足k:无需翻转,直接返回
- 内存管理:正确释放哑节点内存
- 指针操作顺序:注意维护
prev
和cur
的位置关系 - 循环终止条件:外层循环次数严格等于组数
n
6. 代码实现
class Solution {
public:ListNode* reverseKGroup(ListNode* head, int k) {// 计算链表总长度int total = 0;ListNode* cur = head;while(cur) {total++;cur = cur->next;}int groups = total / k; // 完整翻转的组数// 创建哑节点简化操作ListNode* dummy = new ListNode(0);ListNode* prev = dummy;cur = head;// 分组翻转for(int i = 0; i < groups; i++) {ListNode* groupHead = cur; // 记录当前组的原始头节点for(int j = 0; j < k; j++) {ListNode* next = cur->next; // 保存下一个节点cur->next = prev->next; // 头插法核心操作prev->next = cur; // 更新前驱节点的nextcur = next; // 移动当前指针}prev = groupHead; // 前驱指针移动到当前组的末尾}// 连接剩余节点prev->next = cur;// 释放哑节点内存ListNode* res = dummy->next;delete dummy;return res;}
};
代码解析
- 哑节点技巧:
dummy->next
始终指向结果链表的头部,避免处理头节点的特殊情况 - 头插法实现细节:
cur->next = prev->next
:将当前节点插入到前驱节点之后prev->next = cur
:更新前驱节点的next指针- 循环
k
次完成一组节点的翻转
- 指针移动逻辑:
prev
指针在每组翻转后移动到该组的原始头节点位置(即翻转后的末尾节点)cur
指针始终指向待处理的下一个节点
复杂度分析
- 时间复杂度:O(n)
- 计算长度需要O(n)
- 每个节点被处理两次(插入和连接),总体时间复杂度保持线性
- 空间复杂度:O(1)
- 仅使用常数级别的额外空间