题目描述
给你链表的头节点 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-1步),若存在则翻转从慢指针开始的k个节点,将翻转后的子链表连接到结果链表中,再更新指针继续处理下一组;若不足k个节点则直接拼接剩余部分,最终返回哨兵节点的后继即为结果。
算法过程
- 初始化:创建哨兵节点
guard
(简化头节点处理),定义slow
(当前组起始指针)、fast
(探测指针)初始均指向head
,p
(结果链表尾指针)初始指向guard
。 - 循环处理各组:
- 探测k个节点:快指针
fast
向前移动k-1步,若移动后fast
为null
(不足k个节点),则跳出循环。 - 翻转当前组:调用
reverseLink
函数翻转从slow
开始的k个节点,返回翻转后的子链表头节点。 - 连接翻转后的子链表:将
p.next
指向翻转后的头节点,更新p
为当前组原头节点(翻转后变为尾节点)。 - 更新指针到下一组:
slow
和fast
均移动到当前组尾节点的下一个节点(下一组起始位置)。
- 探测k个节点:快指针
- 处理剩余节点:若剩余节点不足k个,
p
会自然连接剩余部分(无需额外操作)。 - 返回结果:返回
guard.next
(哨兵节点的后继即为最终链表头节点)。
辅助函数reverseLink
说明:
输入链表头节点head
和翻转长度k
,翻转这k个节点,返回翻转后的头节点,同时让原头节点(翻转后为尾节点)的next
指向未翻转的剩余部分,确保链表连续。
该算法时间复杂度为O(n)(n为链表长度,每个节点最多被访问2次),空间复杂度为O(1)(仅用常数级指针)。
代码
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
var reverseKGroup = function(head, k) {
let slow = head, fast = head;
let guard = new ListNode();
let p = guard;
while (fast) {
for (let i = 0; i < k-1; i++) {
if (fast) {
fast = fast.next;
} else {
break;
}
}
if (!fast) {
break;
}
p.next = reverseLink(slow, k);
p = slow;
slow = slow.next;
fast = slow;
}
return guard.next;
};
function reverseLink(head, k) {
let p = head, q = p.next;
while (k > 1) {
let tmp = q.next;
q.next = p;
head.next = tmp;
p = q;
q = tmp;
k--;
}
return p;
}