25 k个一组翻转链表

这篇博客详细介绍了如何使用递归和迭代两种方法翻转链表的每k个节点,包括示例解析和时间空间复杂度分析。递归方案通过寻找下一组节点并递归翻转,然后翻转当前组;迭代方案则利用头节点创建虚拟链表,逐组翻转并连接。两种方法都实现了常数额外空间复杂度的高效解决方案。
摘要由CSDN通过智能技术生成

题目

给你一个链表,每 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,为下一轮翻转做准备。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值