62.K 个一组翻转链表
题目
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
- 输入:head = [1,2,3,4,5], k = 2
- 输出:[2,1,4,3,5]
示例 2:
- 输入:head = [1,2,3,4,5], k = 3
- 输出:[3,2,1,4,5]
提示:
- 链表中的节点数目为 n
- 1 <= k <= n <= 5000
- 0 <= Node.val <= 1000
进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?
思路
这一道题其实就是上一题的循环加强版。
上一题是在指定的一个区间做一次反转。
这道题是把整个链表切成一段一段,每段长度是k,然后对每一段都做一次"上一题那样的反转"
所以核心思想就是:
一段一段的处理,每段都用头插法法转,然后把段和段之间连起来。
唯一的新问题就是: 最后一段如果不够k个,就不反转,照样保留,所以每处理一段之前,得先数一数后面还够不够k个节点。
沿用上一题的三个法宝:
- dummy哨兵节点 -- 第一段反转时,前段没有节点,dummy帮我们统一处理边界。
- pre守门人 -- 每一段都有一个pre,是这段前面那个不动的节点。反转这一段后,pre要更新成这一段的尾巴,为下一段做准备。
- 头插法 -- 和上一题一模一样,把段内节点依次摘下插到pre后面。
图解整个过程(head = [1,2,3,4,5], k = 2)
初始,加上 dummy:
pre = 1
dummy → 1 → 2 → 3 → 4 → 5
第一段 [1,2]:数够 2 个,用头插法反转
pre移动这一段的新尾巴,还是1
dummy → 2 → 1 → 3 → 4 → 5
第二段 [3,4]:从新 pre 数够 2 个,反转
dummy → 2 → 1 → 4 → 3 → 5
pre 移动到这一段的新尾巴,这里是3
第三段 [5]:从 pre 往后只有 1 个节点,不够 2 个 → 停止,保持原样
最终结果:
2 → 1 → 4 → 3 → 5 ✅
class Solution {
fun reverseKGroup(head: ListNode?, k: Int): ListNode? {
// 1. 哨兵节点,指向 head,统一处理第一段的边界
val dummy = ListNode(0)
dummy.next = head
// pre 是"当前这一段"前面的那个不动的节点,初始是 dummy
var pre: ListNode = dummy
while (true) {
// 2. 先检查:从 pre 出发,后面是否还有 k 个节点
// end 走 k 步,如果中途碰到 null,说明不足 k 个
var end: ListNode? = pre
for (i in 0 until k) {
end = end?.next
if (end == null) {
// 剩余节点不足 k 个,保持原样,直接返回
return dummy.next
}
}
// 3. 够 k 个,对这一段做头插法反转
// cur 是这段的第一个节点,反转过程中它会被挤到段尾
val cur: ListNode = pre.next!!
for (i in 0 until k - 1) {
// 要移动的节点
val next: ListNode = cur.next!!
// cur 越过 next(把 next 摘出来)
cur.next = next.next
// next 插到 pre 后面
next.next = pre.next
// pre 指向 next,完成头插
pre.next = next
}
// 4. 这一段反转完成,cur 现在是这段的新尾巴
// 把 pre 更新为 cur,为处理下一段做准备
pre = cur
}
}
}
复杂度
- 时间复杂度: O(n):虽然有嵌套循环,但每个节点其实只被"数"一次、被"反转移动"一次,总操作量和节点数成正比。
- 空间复杂度: O(1):只用了 dummy、pre、cur、end、next 几个指针,没有额外空间。
- 邮箱 :charon.chui@gmail.com
- Good Luck!