(每日一道算法题) K 个一组翻转链表
25. K 个一组翻转链表 - 力扣(LeetCode)
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
提示:
- 链表中的节点数目为
n
1 <= k <= n <= 5000
0 <= Node.val <= 1000
解法思路
步骤分析
- 计算链表长度:首先遍历链表,得到总长度
n
,确定需要翻转的组数m = n / k
。 - 虚拟头节点:创建一个虚拟头节点,简化头节点翻转后的连接处理。
- 分组翻转:对每个分组进行头插法翻转,逐个将节点插入到前驱节点之后,形成逆序。
- 连接各组:每完成一组翻转后,更新前驱指针到当前组的末尾,以便连接下一组。
- 处理剩余节点:将剩余不足
k
个的节点直接连接到已处理部分的末尾。
关键点
- 头插法翻转:对于每组节点,通过将每个节点插入到前驱节点之后,实现逆序。
- 前驱指针更新:每完成一组翻转后,前驱指针移动到当前组的末尾,保持后续连接的正确性。
代码解析
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
int n = 0;
ListNode cur = head;
while (cur != null) { // 计算链表总长度
cur = cur.next;
n++;
}
int m = n / k; // 需要翻转的组数
ListNode dummy = new ListNode(-1); // 虚拟头节点
ListNode prev = dummy;
cur = head;
ListNode tmp = null;
for (int i = 0; i < m; i++) { // 处理每组
tmp = cur; // 保存当前组的第一个节点,翻转后变为末尾
for (int j = 0; j < k; j++) { // 头插法翻转当前组
ListNode next = cur.next;
cur.next = prev.next; // 当前节点插入到prev之后
prev.next = cur;
cur = next;
}
prev = tmp; // 前驱指针移动到当前组末尾
}
prev.next = cur; // 连接剩余节点
return dummy.next;
}
}
复杂度分析
- 时间复杂度:O(n),其中
n
为链表长度。遍历链表计算长度耗时 O(n),翻转每组节点耗时 O(n)。 - 空间复杂度:O(1),仅使用常数级别的额外空间。
总结
本解法通过头插法实现每组的翻转,利用虚拟头节点简化连接逻辑,通过前驱指针维护各组之间的连接。思路清晰,代码简洁高效。适用于对链表操作较为熟悉的开发者,重点在于理解头插法的翻转逻辑和指针的更新策略。