K 个一组翻转链表
约 720 字大约 2 分钟
2025-02-28
25. 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)
额外内存空间的算法解决此问题吗?
递归+双指针
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null) return null;
// 区间 [a, b) 包含 k 个待反转元素
ListNode a, b;
a = b = head;
// k次操作,即从头节点向后走k步,到达第k+1个节点。重点,左闭右开。
for (int i = 0; i < k; i++) {
// 不足 k 个,不需要反转,base case。注意先判空再前进
// 假设链表长度为k,b会前进k步,到达null,此时也应该反转,
// 但是如果先前进再判空的话就会直接返回head,不会反转。
if (b == null) return head;
b = b.next;
}
// 反转前 k 个元素
ListNode newHead = reverse(a, b);
// 递归反转后续链表并连接起来
a.next = reverseKGroup(b, k);
return newHead;
}
/* 反转区间 [a, b) 的元素,注意是左闭右开 */
// 不能用左闭右闭,即[a,b-1],区间里只有k个节点。
// 因为如果是[a,b-1],那么while的结束条件应该是cur!=null,但是这样就会一直走到链表末尾才停止循环
// 而不是走到b停止循环,因此这里的b和原来的null一样,相当于一个结束循环的标志点。
ListNode reverse(ListNode a, ListNode b) {
ListNode pre, cur, nxt;
pre = null;
cur = a;
nxt = a;
// while 终止的条件改一下就行了。正常是[a, null),cur != null。重点。
while (cur != b) {
// 备忘
nxt = cur.next;
// 反转。cur==b-1时,nxt=b,cur.next=pre即把b-1指向b-2,所以b-1和b之间就没有连接了,所以[a,b-1]就和后面分开了。
cur.next = pre;
// 前进
pre = cur;
cur = nxt;
}
// 返回反转后的头结点
return pre;
}
}
- 时间复杂度:O(n),其中 n 为链表的长度。
head
指针会在 O(⌊kn⌋) 个节点上停留,每次停留需要进行一次 O(k) 的翻转操作。 - 空间复杂度:O(1),我们只需要建立常数个变量。