LeetCode 面试经典 150_链表_K 个一组翻转链表(61_25_C++_困难)(四指针法;头插法)
LeetCode 面试经典 150_链表_K 个一组翻转链表(61_25_C++_困难)
- 题目描述:
- 输入输出样例:
- 题解:
- 解题思路:
- 思路一(四指针法):
- 思路二(头插法):
- 代码实现
- 代码实现(思路一(四指针法)):
- 代码实现(思路二(头插法)):
- 以思路二进行代码调试
题目描述:
给你链表的头节点 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
题解:
解题思路:
思路一(四指针法):
1、通过题目分析,在每次翻转前需要进行个数的判断,若满足再将k个结点翻转,将翻转后的答案进行连接。
我们发现我们在进行翻转的时候需要保存k个结点的首和尾(kHead和kTail),并且还需要保存kHead之前的一个结点(ansTail)和kTail之后的一个结点(next_kHead),方便将翻转后的链表进行连接和剩余结点的处理,因此我们需要四个指针(kHead、kTail、ansTail、next_kHead)。
具体实现思路请看下图:

2、复杂度分析:
① 时间复杂度:O(n),n 代表链表中元素的个数,只遍历了一遍列表。
② 空间复杂度:O(1),使用常数个内存空间(只对源节点的next指向进行更改)。
思路二(头插法):
1、通过题目分析,在每次翻转前需要进行个数的判断,若满足再将k个结点翻转,将翻转后的答案进行连接。
我们在翻转时可将要 翻转的 k 结点采用头插法进行翻转。在每次进行翻转后更新下一次插入的头结点即可。
2、复杂度分析:
① 时间复杂度:O(n),n 代表链表中元素的个数,只遍历了一遍列表。
② 空间复杂度:O(1),使用常数个内存空间(只对源节点的next指向进行更改)。
代码实现
代码实现(思路一(四指针法)):
//判断剩余长度是否>=k,不够则返回nullptr,够则返回k个长度链表的尾结点
ListNode *judgeLen_k(ListNode *kHead,int k){while(k-1){if(kHead==nullptr){return nullptr;}kHead=kHead->next;--k;}return kHead;
} //翻转固定个数的链表,返回翻转后的头结点
ListNode *reverseList_k(ListNode *kHead,int k){ListNode *pre=nullptr,*r=kHead,*tmp=kHead;while(k){r=r->next;tmp->next=pre;pre=tmp;tmp=r;--k;}return pre;
} //K 个一组翻转链表
ListNode* reverseKGroup(ListNode* head, int k) {ListNode *dummyHead=new ListNode(0); //存储答案的尾结点 ListNode *ansTail=dummyHead;//交换前,k个结点的头ListNode *kHead=head;//交换前,k个结点的末尾,不够k个则为nullptr ListNode *kTail=judgeLen_k(kHead,k);//保存下一个区间的头ListNode *next_kHead=nullptr;while(kTail!=nullptr){//保存下一个区间的头next_kHead=kTail->next;//翻转k个结点//可以和下行代码合并 ansTail->next=reverseList_k(kHead,k)reverseList_k(kHead,k);//将k个结点翻转后的链表,连接到答案列表 ansTail->next=kTail;kHead->next=next_kHead; //可最后进行一次连接//更新答案链表的尾结点ansTail=kHead;//更新交换前,k个结点的头 kHead=next_kHead; //判断之后的结点是否够k个 kTail=judgeLen_k(next_kHead,k);} ListNode *ansHead=dummyHead->next;delete dummyHead;return ansHead;
}
代码实现(思路二(头插法)):
class Solution2 {
private:// 判断是否满足 k 个,返回下一个 k 个元素的开始位置ListNode *judge_K(ListNode *curNode, int k) {// 遍历 k-1 次,检查是否能找到 k 个节点while(k-1) {// 如果当前节点为空,则返回 nullptr,表示剩余节点不足 k 个if(curNode == nullptr) {return nullptr;}// 否则继续移动到下一个节点curNode = curNode->next;k--; // 剩余的 k 值减 1}// 返回当前节点,表示已经找到 k 个节点return curNode;}public:ListNode* reverseKGroup(ListNode* head, int k) {// 如果链表中节点数量小于 k,直接返回原链表,不进行反转if(judge_K(head, k) == nullptr) {return head;}// 创建一个哑节点,方便处理链表的头节点ListNode *dummyHead = new ListNode(-1);// dummyHead 指向链表的头部ListNode *curNode = head;ListNode *curHead = dummyHead; // curHead 是当前操作的节点的前一个节点ListNode *nextNode;// 开始反转链表的每个 k 个节点while(judge_K(curNode, k) != nullptr) {// k_tail 是当前反转部分的尾节点ListNode *k_tail = curNode;// 反转 k 个节点for(int i = 0; i < k; i++) {// 保存当前节点的下一个节点nextNode = curNode->next;// 将当前节点插入到 curHead 后面curNode->next = curHead->next;curHead->next = curNode;// 当前节点指向下一个节点,继续反转curNode = nextNode;}// 更新 curHead 为当前反转部分的尾节点curHead = k_tail;}// 最后更新 curHead 的 next 指向剩下部分的节点curHead->next = nextNode;// 返回反转后的链表头部,跳过哑节点curHead = dummyHead->next;// 删除哑节点,释放内存delete dummyHead;return curHead;}
};
以思路二进行代码调试
#include<iostream>
#include<vector>
using namespace std;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){}
}; //尾插法创建单链表
ListNode *createList(vector<int> arr){ListNode *head=nullptr,*tail=nullptr;for(const auto &val:arr){if(head==nullptr){head=tail=new ListNode(val);}else{tail->next=new ListNode(val);tail=tail->next;}}return head;
} class Solution2 {
private:// 判断是否满足 k 个,返回下一个 k 个元素的开始位置ListNode *judge_K(ListNode *curNode, int k) {// 遍历 k-1 次,检查是否能找到 k 个节点while(k-1) {// 如果当前节点为空,则返回 nullptr,表示剩余节点不足 k 个if(curNode == nullptr) {return nullptr;}// 否则继续移动到下一个节点curNode = curNode->next;k--; // 剩余的 k 值减 1}// 返回当前节点,表示已经找到 k 个节点return curNode;}public:ListNode* reverseKGroup(ListNode* head, int k) {// 如果链表中节点数量小于 k,直接返回原链表,不进行反转if(judge_K(head, k) == nullptr) {return head;}// 创建一个哑节点,方便处理链表的头节点ListNode *dummyHead = new ListNode(-1);// dummyHead 指向链表的头部ListNode *curNode = head;ListNode *curHead = dummyHead; // curHead 是当前操作的节点的前一个节点ListNode *nextNode;// 开始反转链表的每个 k 个节点while(judge_K(curNode, k) != nullptr) {// k_tail 是当前反转部分的尾节点ListNode *k_tail = curNode;// 反转 k 个节点for(int i = 0; i < k; i++) {// 保存当前节点的下一个节点nextNode = curNode->next;// 将当前节点插入到 curHead 后面curNode->next = curHead->next;curHead->next = curNode;// 当前节点指向下一个节点,继续反转curNode = nextNode;}// 更新 curHead 为当前反转部分的尾节点curHead = k_tail;}// 最后更新 curHead 的 next 指向剩下部分的节点curHead->next = nextNode;// 返回反转后的链表头部,跳过哑节点curHead = dummyHead->next;// 删除哑节点,释放内存delete dummyHead;return curHead;}
};int main(){vector<int> a={1,2,3,4,5};int k=2;Solution2 s;ListNode *head=createList(a);ListNode *test=s.reverseKGroup(head,k); while(test!=nullptr){cout<<test->val<<"->";test=test->next;}cout<<"null"; return 0;
}
LeetCode 面试经典 150_链表_K 个一组翻转链表(61_25)原题链接
欢迎大家和我沟通交流(✿◠‿◠)
