综合题解:K 个一组翻转链表(迭代法)
解题思路
本问题要求以 常数空间复杂度 完成链表分组翻转。核心思想是通过 哑节点(dummy node) 简化边界处理,并利用 指针分段操作 实现每组翻转。具体步骤如下:
- 哑节点初始化:创建虚拟头节点
dummy
,统一处理链表头部的翻转逻辑。 - 分组探测与翻转:通过
pre
指针标记当前组的前驱,探测每组起始start
和终止end
,若剩余节点不足k
则终止。 - 子链表翻转:断开当前组与后续链表,翻转当前组后重新连接前后链表。
- 指针重置:更新
pre
指针为当前组的末尾,继续处理下一组。
算法流程
-
初始化
- 创建哑节点
dummy
,其next
指向原链表头head
。 - 初始化
pre
指针指向dummy
,用于标记当前组的前驱节点。
- 创建哑节点
-
循环探测每组
- 通过
pre
找到当前组的起始节点start = pre.next
。 - 移动
end
指针k
次,找到当前组的终止节点。若中途遇到null
,说明剩余节点不足k
,终止循环。
- 通过
-
子链表翻转
- 记录下一组的起始节点
next_group = end.next
。 - 断开当前组与后续链表的连接:
end.next = null
。 - 调用翻转函数
reverse(start)
,返回翻转后的新头节点new_head
。 - 将前驱组与新翻转组连接:
pre.next = new_head
。 - 将翻转后的组的末尾与原后续链表连接:
start.next = next_group
。
- 记录下一组的起始节点
-
指针更新
- 更新
pre
指针到当前组的末尾(即原start
节点)。 - 重复步骤 2-4,直到处理完所有完整分组。
- 更新
复杂度分析
- 时间复杂度:O(n),每个节点被遍历两次(分组探测 + 翻转)。
- 空间复杂度:O(1),仅使用固定数量的指针变量。
#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:
ListNode* reverseKGroup(ListNode* head, int k) {
// 创建哑节点并初始化指针
ListNode dummy(0);
dummy.next = head;
ListNode* pre = &dummy; // 当前组的前驱节点
while (true) {
// 1. 探测当前组的起始和终止节点
ListNode* start = pre->next;
ListNode* end = pre;
// 移动end指针k次,找到当前组的结尾
for (int i = 0; i < k; ++i) {
end = end->next;
if (!end) return dummy.next; // 剩余节点不足k个,直接返回
}
// 2. 记录下一组的起始节点并断开当前组
ListNode* next_group = end->next;
end->next = nullptr;
// 3. 翻转当前组并重新连接
ListNode* new_head = reverse(start);
pre->next = new_head; // 前驱指向新头
start->next = next_group; // 原头(现尾)连接下一组
// 4. 更新pre指针到当前组的末尾
pre = start;
}
}
private:
// 辅助函数:翻转单个链表
ListNode* reverse(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr) {
ListNode* next_node = curr->next;
curr->next = prev;
prev = curr;
curr = next_node;
}
return prev;
}
};
// 测试代码
void printList(ListNode* head) {
while (head) {
cout << head->val << " ";
head = head->next;
}
cout << endl;
}
int main() {
// 构建测试链表: 1->2->3->4->5
ListNode* node5 = new ListNode(5);
ListNode* node4 = new ListNode(4, node5);
ListNode* node3 = new ListNode(3, node4);
ListNode* node2 = new ListNode(2, node3);
ListNode* node1 = new ListNode(1, node2);
Solution sol;
ListNode* result = sol.reverseKGroup(node1, 3);
printList(result); // 应输出: 3 2 1 4 5
// 清理内存(实际面试可能不需要)
ListNode* curr = result;
while (curr) {
ListNode* temp = curr;
curr = curr->next;
delete temp;
}
return 0;
}
步骤
1. 辅助翻转函数 reverse()
作用:翻转单个链表段,返回新头节点。
实现步骤:
- 初始化
prev
和curr
指针。 - 遍历链表,逐个反转指针方向。
- 返回翻转后的头节点
prev。
2. 主函数框架搭建
作用:初始化哑节点和指针,构建循环结构。
实现步骤:
- 创建哑节点
dummy
,简化头节点处理。 - 初始化
pre
指针指向哑节点,用于标记当前组的前驱。 - 构建循环,处理每一组节点。
3. 分组探测逻辑
作用:找到当前组的起始 start
和终止 end
节点。
实现步骤:
- 从
pre->next
开始,移动end
指针k
次。 - 若中途遇到空指针,说明剩余节点不足
k
个,直接返回结果。
4. 断开当前组并翻转
作用:将当前组与后续链表断开,翻转后重新连接。
实现步骤:
- 记录下一组的起始节点
next_group
。 - 断开当前组:
end->next = nullptr
。 - 翻转当前组,将前驱
pre
指向新头节点。 - 原组的头(现尾)连接下一组:
start->next = next_group
。
5. 更新指针并进入下一轮
作用:将 pre
指针移动到当前组的末尾,处理下一组。