题目
给你一个链表,每 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]
解释:1,2,3, 4,5-> 3,2,1, 4,5
示例 3:
输入:head = [1,2,3,4,5], k = 1
输出:[1,2,3,4,5]
示例 4:
输入:head = [1], k = 1
输出:[1]
- 递归版
/**
* 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:
ListNode* reverseKGroup(ListNode* head, int k) {
//如果链表为空或只有一个节点或k<2,返回head
if(head==nullptr || head->next == nullptr || k<2)
return head;
ListNode *next_group = head;//指向下一组k个节点的第一个节点
//寻找next_group的值,如果剩下的节点大于k个则找到从第一个节点开始的第k+1个节点,即是next_group所指的节点
for(int i=0; i<k; i++)
if(next_group)
next_group = next_group->next;
else
return head;
//递归调用该函数,翻转下一组链表
ListNode *new_next_group = reverseKGroup(next_group, k);
//翻转该组链表,pre指向首节点的前一个节点,也是翻转后链表的最后一个节点,cur指向当前翻转的节点
ListNode *pre = NULL, *cur = head;
while(cur!=next_group){
ListNode *next = cur->next;
cur->next = pre ? pre:new_next_group;//如果pre为空,则表示此时的cur是首节点,翻转后该节点会成为末节点,它的后继节点为下一组翻转后链表的首节点
pre = cur;
cur = next;
}
return pre;//返回翻转后链表的首节点
}
};
- 时间复杂度O(n)
- 空间复杂度O(1)
- 思路
- 计算链表中第k+1个节点,它是下一组要翻转节点的首节点,用next_group指向该节点。递归调用该函数,传入参数为下一组中的首节点和k,得到下一组翻转后链表的首节点,该节点需要与本组翻转后链表的末节点相连。
- 翻转本组链表,pre始终指向翻转后的最后一个节点,cur指向当前在翻转的节点,next为cur的后继。当cur指向的是本组链表节点时,循环继续。先修改cur的后继,如果pre为空,表示此时的cur是翻转之后的末节点,需要与下一组翻转后链表的首节点相连,如果不是空则将pre设置为cur的后继。pre指向cur,cur指向next。
- 返回翻转后本组链表的末节点。
- 迭代版
/**
* 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:
ListNode* reverseKGroup(ListNode* head, int k) {
//如果链表为空或只有一个节点或k<2,返回head
if(head==nullptr || head->next==nullptr || k<2) return head;
ListNode dummy(-1);//定义头结点
dummy.next = head;
//遍历链表,每k个一组翻转链表
//pre指向当前翻转链表的首节点的前一个节点,end指向当前翻转链表的最后一个节点
for(ListNode *pre = &dummy, *end = head; end; end = pre->next){
//遍历链表,找到当前该组链表的第k个节点,如果为空表示当前这组不足k个,不需要翻转这组节点,直接退出
for(int i=1; i<k&&end; i++)
end = end->next;
if(end == nullptr) break;
//调用reverse函数,pre的值被修改为翻转后链表的最后一个节点
//传入的三个参数分别为:头节点、首节点、末节点
pre = reverse(pre, pre->next,end);
}
return dummy.next;
}
//翻转begin和end之间的节点,返回翻转后链表的最后一个节点
ListNode* reverse(ListNode* pre, ListNode* begin, ListNode* end){
ListNode *end_next = end->next;//记录下一组待翻转链表的首节点,需要将翻转后链表的末节点与之相连
//遍历待翻转区间,p指向当前节点的前一个节点,cur指向当前节点,next为cur的后继
//迭代版如果最后一组刚好有k个节点,next可能会越界,即cur为空,next不能为空指针的后继,此时cur与end_next相等,但是for循环是先赋值,再判断。故在进入下一次循环给next赋值时要先判断是否为空
for(ListNode *p = begin, *cur = p->next,*next = cur->next; cur!=end_next;p = cur, cur = next, next = next?next->next:nullptr){
cur->next = p;//将当前节点的后继改为它的前一个节点
}
//将翻转后的链表与前一组链表的末节点相连,与后一组链表的首节点相连
//翻转后begin指向了翻转后链表的最后一个节点
//翻转后end指向了翻转后链表的第一个节点
//pre为上一组链表的末节点,将它的后继设置为end
begin->next = end_next;
pre->next = end;
return begin;
}
};
- 时间复杂度O(n)
- 空间复杂度O(1)
- 思路
- 设置一个头节点,然后for循环遍历链表,每k个一组,调用reverse函数进行翻转。每轮循环都要计算当前k个一组中的最后一个节点,用end指向它,pre始终指向当前组第一个节点的前一个节点。如果end为空则表示当前组不足k个,已到达链表末尾,退出循环,返回头节点的后继。调用reverse翻转当前k个节点,将pre设置为翻转后当前k个节点的最后一个节点。
- reverse函数传入三个参数,pre表示当前k个节点中首节点的前一个节点,begin为当前节点中的首节点,end为末节点,函数返回翻转后链表的末节点。
- end_next记录下一组k个节点的首节点。for循环翻转当前k个节点,翻转结束后再将翻转后的链表与左右两边的链表接起来。
- for循环中,可以把翻转k个链表看成将箭头方向反转。即翻转这5个节点,1->2->3->4->5到1<-2<-3<-4<-5。p指向翻转后链表的最后一个节点,cur指向尚未翻转的第一个节点,next为cur的后继。当cur为end_next时表示该组节点已经翻转完毕,退出循环。
- begin为翻转后链表的末节点,将它的后继设置为end_next,end为翻转后链表的首节点,将pre的后继设置为end。
- 返回begin,为下一轮翻转做准备。