题解 | 链表中的节点每k个一组翻转 (有图解,一个尽量减少遍历次数的非递归方法)
链表中的节点每k个一组翻转
https://www.nowcoder.com/practice/b49c3dc907814e9bbfa8437c251b028e
主要思路
因为是从上一题刷过来的,所以理所当然地沿用上一题的翻转思路。
首先因为上一题是m到n进行翻转,这一题相当于拆成了许多个m到n进行翻转,所以核心翻转部分可以直接抄。
首先想了想该怎么做:
想法一:先遍历一遍数出有多少个结点,然后再从头分块进行翻转
想法二:先根据k分块翻转,直到最后遇到不足k时再重新反转一遍,这样只走一遍就行
虽然两个都是O(n),但感觉思路二需要遍历的次数更少,所以选思路二了。
情况讨论(假设有n个结点):
特殊情况:
- k = 1,不反转
- head为空或者只有链表一个结点
正常情况:
- k >= n:不反转(或者反转2次)
- k < n:
- 能整除:直接返回头结点
- 不能整除:剩下部分再反转
缺点:当k=n时需要翻转2次
以k=3为例:
代码
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
// 特殊情况特殊对待。
if (head == nullptr ||
head->next == nullptr ||
k == 1)
return head;
// 正常情况
// 1. (初始化要用的变量)创建临时头结点并连接
ListNode* tmp_head = new ListNode(-1);
tmp_head->next = head;
// 1.2 创建交换用的指针、计数
// p:前面不动的指针,p指q的pre结点
// q:指向“把前面的结点往p后面扔”的结点。
ListNode* p = tmp_head, *q = head;
ListNode* t = head->next; // 此时已经排除了head为空的情况
// 用于for内计数,由于后面需要用到i,所以不放for里创建
int i = 1;
// 2. 开倒!
while (q->next != nullptr) {
for (i = 1; i < k && q->next != nullptr; i++) { // k个结点要做k-1次反转
t = q->next;
q->next = t->next;
t->next = p->next;
p->next = t;
}
// 往后跳一格,前往下一个组
if (i == k && // 表示for循环完整结束
q->next != nullptr){ // 防止超内存
p = q; // p移动到q的位置,因为p的前结点不会变
q = q->next;
// 这里不更新t,因为可能碰到null的情况,所以放到for里面就行了
}
}
// 3. 看看是否需要再翻转
// 如果i=k,说明for循环正常结束了。反之就说明for循环没有正常结束
if (i != k) {
// 先更新指针,让q回到p的下一结点,也就是t
q = t;
while (q->next != nullptr) { // 再走一遍流程,反转一遍
t = q->next;
q->next = t->next;
t->next = p->next;
p->next = t;
}
}
return tmp_head->next;
}
};
