【LeetCode-链表】K个一组反转链表
题目描述
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
题目链接: https://leetcode-cn.com/problems/reverse-nodes-in-k-group/
思路1
对链表每 k 个节点执行一次普通的链表反转,过程如下图
图来自这里
具体代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head==nullptr || k<2) return head;
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* preTail = dummy; // 前一段链表的链表尾
ListNode* curTail = dummy; // 当前这一段反转前的链表尾,反转后的链表头
while(curTail->next!=nullptr){
int cnt = 0;
while(cnt<k && curTail!=nullptr){
curTail = curTail->next;
cnt++;
}
if(curTail==nullptr) return dummy->next;
ListNode* curHead = preTail->next; // 当前这一段反转前的链表头,反转后的链表尾
ListNode* next = curTail->next;
curTail->next = nullptr; // 别忘了这一句
preTail->next = reverse(curHead);
curHead->next = next;
preTail = curHead;
curTail = curHead;
}
return dummy->next;
}
/*单链表反转*/
ListNode* reverse(ListNode* head){
if(head==nullptr) return head;
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode* next = nullptr;
while(cur!=nullptr){
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
思路2
思路 2 是我最初的做法,和思路 1 大致相似,但是写的不太好理解,推荐思路 1 或者思路 3.
根据最基础的链表反转来做。因为题目要求如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序,所以先求要翻转几段链表(链表长度/k),然后分别对每段子链表应用链表翻转,需要注意的是反转后指针的变化,要防止链表断裂,具体代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head==nullptr || head->next==nullptr || k==1){
return head;
}
ListNode* newHead = new ListNode(0); // 新的链表头
newHead->next = head;
ListNode* pre = newHead; // 存储前一段链表反转后的尾结点,用于连接各段链表,防止链表断裂
ListNode* preNode = nullptr; // 用于反转链表节点
ListNode* curNode = head;
ListNode* nextNode = nullptr;
int length = getListLength(head); // 获取链表长度
int loop = length/k; // 要反转loop段长为k的链表
while(loop){
ListNode* firstNode = curNode; // 链表反转, firstNode记录链表反转前的第一个节点(反转后最后一个节点,用于连接链表段,防止链表断裂)
for(int i=1; i<=k && curNode!=nullptr; i++){
nextNode = curNode->next;
curNode->next = preNode;
preNode = curNode;
curNode = nextNode;
}
pre->next = preNode; //将前一段反转后链表的链表尾和当前这一段相连
firstNode->next = curNode; // 将当前这一段的反转后的链表尾指向下一段的未反转的链表头
pre = firstNode; // 更新pre为反转后链表的尾结点
loop--;
}
return newHead->next;
}
int getListLength(ListNode* head){
int length = 0;
while(head!=nullptr){
head = head->next;
length++;
}
return length;
}
};
- 时间复杂度:O(n)
遍历链表一遍。 - 空间复杂度: O(1)
思路3
使用栈来做。将节点入栈后出栈再连接就是反序了。用栈也要注意,最后一段链表的节点个数可能不够 k 个,这个时候就不需要出栈反转了,只需要记录下前一段链表的链表尾 preTail 和当前段链表的链表头 partHead,然后 preTail->next = partHead 即可。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head==nullptr || k<2) return head;
stack<ListNode*> s;
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* cur = head;
ListNode* partHead; // 当前这一段反转前的链表头
ListNode* preTail = dummy; // 前一段的链表尾
while(cur!=nullptr){
int cnt = 0;
partHead = cur;
while(cnt<k && cur!=nullptr){
s.push(cur);
cur = cur->next;
cnt++;
}
if(s.size()==k){
while(!s.empty()){
ListNode* node = s.top(); s.pop();
preTail->next = node;
preTail = preTail->next;
node->next = nullptr; // 别忘了这一句
}
}else preTail->next = partHead; // 最后一段不够 k 个,直接连接
}
return dummy->next;
}
};
总结
和链表反转的题目都可以根据最基础的链表反转这题的思想来做。代码如下:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==nullptr || head->next==nullptr){
return head;
}
ListNode* preNode = nullptr;
ListNode* curNode = head;
ListNode* nextNode = nullptr;
while(curNode!=nullptr){
nextNode = curNode->next;
curNode->next = preNode;
preNode = curNode;
curNode = nextNode;
}
return preNode; // 注意返回的是preNode,因为curNode此时为nullptr
}
};
这样反转链表后有两个非常有用的性质:1、反转结束后preNode就是反转后链表的头结点;2、反转结束后curNode是反转结束后preNode原先指向的那个节点,。(如果把整个链表分成多段分别翻转,这个性质就非常有用,比如当前的这一题“K个一组反转链表”),如下图
相关题目
1、反转链表:https://leetcode-cn.com/problems/reverse-linked-list/ ,题解
2、反转链表II:https://leetcode-cn.com/problems/reverse-linked-list-ii/ ,题解
参考
1、https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/
2、https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/kge-yi-zu-fan-zhuan-lian-biao-by-powcai/