25. K 个一组翻转链表
1.模拟交换
-
本题比较经典,建议多练习。如果是初学,建议在完成24. 两两交换链表中的节点,可以理解哑巴节点的作用,能理解链表操作的特性再练习。
-
本题和两两反转链表的原理是相同的,但是需要做到任意K个反转,并且在不足K个的时候以正常顺序返回不足K个的这一部分。
-
但是本题实现起来却有一定难度,这里提及解决链表问题的一个重要心得:
大量设变量,能有效降低分析复杂度 。
因为链表的结构,导致在解决问题时,会涉及大量的->的操作,往往在操作之后,原有的顺序结构发生了改变,必须记录下某一些位置的节点,这些情况就必须清楚的把需要的节点都规划出来,否则操作会非常繁琐易错。
1.1子区间反转
-
我们可以设计一个子函数,将传入的head至tail之间的链表节点全部反转,并且返回反转之后的头尾节点。
-
这里设计的变量有:
- p:用来遍历节点,指的是当前操作的这个节点。
- pre:指的是"前面那个节点",初始为空,开始反转之后,在节点p操作完成后,p成为了新的pre,供下一个p连接。
-
为了同时返回两个变量,使用了STL中的pair,本函数可以返回一个以原本的tail为头节点,反转了顺序的、长度为K的链表,末尾为nullptr。
-
pair<ListNode*, ListNode*> reverseChildGroup(ListNode* head,ListNode* tail){ ListNode *pre=nullptr; ListNode *p=head;//初始,"前面那个节点"是空节点 while(pre!=tail){ ListNode *temp=p->next; p->next=pre; pre=p; p=temp; } return {tail,head}; }
1.2每K个反转
-
上面用子函数解决了K个之内的反转,接下里就要解决K个内部反转之后怎么正确连接节点的问题。
-
同样,关键仍然是清楚的规划好变量,把复杂问题条理化:
- head_now、tail_now:这里的两个变量用于记录当前K区间的头尾位置。在反转之后,我们用这两个变量记录反转后的新head、tail。
- 下面两个非常关键,通过他们完成了区间之间的正确连接,但是要理解他们的意义,明白什么时候应该更新他们的数值:
- tail_pre:上一个K区间,反转后的尾部节点,用于和当前K区间的头节点连接(K区间的头节点可能是反转后的新头节点,也可能因为本区间数量不足K个因而没有反抓,需要在函数中分类讨论)。
- 初始值:应当是哑巴节点,我习惯设为result。
- 变化:在当前K区间反转后发生变化,新值为反反转函数返回的新tail。
- head_now:因为我们每向前走K个节点,就需要返回来进行反转,此时会丢失下一个K区间开头的位置。因此我们需要在反抓前,就把下一个K区间开头的节点记录下来,以供使用。
- 初始值:就是题目输入的head节点。
- 变化:反转函数前,是反转前tail_now的下一个元素。
-
class Solution { public: pair<ListNode*, ListNode*> reverseChildGroup(ListNode* head,ListNode* tail){ ListNode *pre=nullptr; ListNode *p=head;//初始,"前面那个节点"是空节点 while(pre!=tail){ ListNode *temp=p->next; p->next=pre; pre=p; p=temp; } return {tail,head}; } ListNode* reverseKGroup(ListNode* head, int k) { ListNode *result,*p,*head_now,*tail_now,*head_next,*tail_pre; result=new ListNode(); result->next=head; tail_pre=result; head_next=head; while(head_next!=nullptr){ head_now=head_next; tail_now=head_next; for(int i=0;i<k-1;i++){ tail_now=tail_now->next; if(tail_now==nullptr){ //注意因为上面的反转函数,tail->next总是nullptr //如果最后一部分不满K个,则不反转,要把前一个区间的尾部正确连接。 tail_pre->next=head_now; return result->next; } } head_next=tail_now->next;//反转操作会让我们找不到下一个区间的入口,这里保留下来 pair<ListNode*, ListNode*> pair1=reverseChildGroup(head_now,tail_now); head_now=pair1.first; tail_now=pair1.second;//head_now、tail_now反转后更新 tail_pre->next=head_now;//本区间已经完成反转,可以将上一区间尾部连接到本区间 tail_pre=tail_now; } return result->next; } };