📝 题目描述

题目链接K 个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例:

示例 1:

1
2
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

1
2
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

💡 解题思路

方法一:模拟✅️

首先创建一个哨兵节点,使得第一个子链表的头节点 head 不再特殊化。

我们需要把链表节点按照 k 个一组分组,所以可以使用一个指针 head 依次指向每组的头节点。这个指针每次向前移动 k 步,直至链表结尾。对于每个分组,我们先判断它的长度是否大于等于 k。若是,我们就翻转这部分链表,否则不需要翻转。

接下来的问题就是如何翻转一个分组内的子链表。翻转一个链表并不难,过程可以参考“反转链表”。但是对于一个子链表,除了翻转其本身之外,还需要将子链表的头部与上一个子链表连接,以及子链表的尾部与下一个子链表连接。

因此,在翻转子链表的时候,我们不仅需要子链表头节点 head,还需要有 head 的上一个节点 prev,以便翻转完后把子链表再接回 prev

反复移动指针 head 与 pre,对 head 所指向的子链表进行翻转,直到结尾,我们就得到了答案。最后返回哨兵节点的下一个节点作为答案即可。

方法二:数组模拟

构建一个数组,将所有链表的指针存储,然后利用 reverse 函数快速翻转,并然后逐个连起链表。

🔧 代码实现

1、模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* Definition for singly-linked list.
* 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:
// 翻转一个子链表,并且返回新的头与尾
pair<ListNode*, ListNode*> myReverse(ListNode* head, ListNode* tail) {
ListNode* prev = nullptr;
ListNode* current = head;
while (prev != tail) {
ListNode* next = current->next;
current->next = prev;
// 准备下一波
prev = current;
current = next;
}
return {tail, head};
}

ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* dummy = new ListNode(-1, head);
ListNode* prev = dummy;

while (head) {
ListNode* tail = prev;
// 查看剩余部分长度是否大于等于 k
for (int i = 0; i < k; ++i) {
tail = tail->next;

// tail为空,则说明不足k个,直接返回即可
if (tail == nullptr) {
return dummy->next;
}
}

ListNode* next = tail->next;

// 这里是 C++17 的写法,也可以写成
// pair<ListNode*, ListNode*> result = myReverse(head, tail);
// head = result.first;
// tail = result.second;
tie(head, tail) = myReverse(head, tail);

// 把子链表重新接回原链表
prev->next = head;
tail->next = next;

// 准备下一波
prev = tail;
head = next;
}

ListNode* ans = dummy -> next;
delete dummy;
return ans;
}
};

2、数组模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if (!head || k == 1) return head; // 边界情况快速返回

vector<ListNode*> list;
ListNode* ptr = head;
while (ptr != nullptr) {
list.push_back(ptr);
ptr = ptr -> next;
}

int start = 0;
// 注意 <=,确保哪怕剩余节点正好等于 k 也能翻转
while (start + k <= list.size()) {
reverse(list.begin() + start, list.begin() + start + k);
start += k;
}

// 遍历到 size - 1,确保每一个节点都连接到下一个节点
for (int i = 0; i < list.size() - 1; i++) {
list[i]->next = list[i + 1];
}

// 最后一个节点的 next 置为 nullptr,防止原有的指针带来循环
list.back()->next = nullptr;

return list[0];
}
};

📊 复杂度分析

1、模拟

  • 时间复杂度O(n)O(n),其中 n 为链表的长度。head 指针会在 O(⌊kn​⌋) 个节点上停留,每次停留需要进行一次 O(k) 的翻转操作。
  • 空间复杂度O(1)O(1),我们只需要建立常数个变量。

2、数组模拟

  • 时间复杂度O(n)O(n),遍历存储一遍节点,翻转访问一遍节点,重连访问一遍节点。
  • 空间复杂度O(n)O(n)

🎯 总结

  • 核心思想:学会本题与“反转链表”的关系。