玩转链表之反转链表

一、反转链表

解法一:迭代(推荐)

  • 优先处理空链表,空链表无需反转
  • 设置前序和当前指针
  • 遍历链表,每到一个节点,断开当前节点与后一节点的指针,用临时变量记录后一节点,让当前节点指向前一节点
  • 更新前序与当前指针
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
    	if(pHead==NULL||pHead->next==NULL)
    		return pHead;
        ListNode*pre=NULL,*cur=pHead;
        while(cur)
        {
        	//断开链表,要记录后续一个
            ListNode*tmp=cur->next;
            //当前的next指向前一个
            cur->next=pre;
            //前一个更新为当前
            pre=cur;
            //头节点更新为其原下一节点
            cur=tmp;
        }
        return pre;
    }
};

图示:
在这里插入图片描述

时间复杂度O(n),遍历链表一次
空间复杂度O(1),未使用额外空间

解法二:递归

  • 对每个节点递归向后遍历
  • 然后向前依次逆转两个节点
  • 连接逆转后的尾节点和这一级的节点
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
    	//这里必须都判断,后续程序使用了pHead->next->next=pHead,不判断下一节点是否为空,直接运行会报错
        if(pHead==NULL||pHead->next==NULL)
            return pHead;
        //反转下一个
        ListNode*newHead=ReverseList(pHead->next);
        pHead->next->next=pHead;//逆转操作
        pHead->next=NULL;//断开原有指向,设置空节点
        return newHead;
    }
};

做这类题目优先考虑特殊情况,当头指针为空,或其next指针为空时,反转后结果为其本身,直接返回本身

pHead->next->next=pHead;

这一步是让下一个节点的next指针指向该节点,通过递归实现指向的全部反转,因此前置判断要加上pHead->next==NULL,以避免对NULL操作报错
时间复杂度O(n),递归遍历链表一次
空间复杂度O(n),递归栈深度为链表长度n

解法三:借用容器

这属于暴力求解了,可以用多种容器实现,
可以进行链表交换,也可以进行链表值的交换来完成题目,不推荐
用vector容器

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        vector<ListNode*>v;
        while(pHead)
        {
            v.push_back(pHead);//存储指针节点
            pHead=pHead->next;
        }
        reverse(v.begin(),v.end());//容器内反转
        ListNode*newHead=v[0];//设置头尾
        ListNode*end=newHead;
        for(int i=0;i<v.size();i++)
        {
            end->next=v[i];//进行链表创建
            end=end->next;
        }
        end->next=NULL;
        return newHead;
    }
};

用stack,和vector一样,不在多说

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        stack<ListNode*>s;
        while(pHead)
        {
            s.push(pHead);
            pHead=pHead->next;
        }
        if(s.empty())
            return NULL;
        ListNode*newHead=s.top();
        s.pop();
        ListNode*end=newHead;
        while(!s.empty())
        {
            end->next=s.top();
            s.pop();
            end=end->next;
        }
        end->next=NULL;
        return newHead;
    }
};

重新构建链表
时间复杂度O(n),遍历链表及容器n的常数次
空间复杂度O(n),额外使用空间存值

二、指定区间内反转链表

解法一:迭代(推荐)

  • 使用虚拟头部,便于表头位置的反转
  • 使用前序与当前指针
  • 遍历到m位置
  • 对m到n位置的节点,依次断掉指向后续的指针,反转其指向
class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // write code here
        //非必要判断
        if(head==NULL||head->next==NULL||m==n)
            return head;
        //创建一个哨兵位,虚拟节点,这个赋值不知到有啥讲究,这里是赋为-1
        ListNode*dummy=new ListNode(-1);
        dummy->next=head;
        ListNode*pre=dummy,*cur=head//设置前序节点和当前节点
        for(int i=0;i<m-1;i++)//找到m节点
        {
        	pre=cur;
        	cur=cur->next;
        }
        for(int i=m;i<n;i++)//反转m到n的链表
        {
           ListNode*tmp=cur->next;
            cur->next=tmp->next;
            tmp->next=pre->next;
            pre->next=tmp;
        }
        return dummy->next;
    }
};

在全部反转的基础上,设置区间,先找到区间头节点,再进行区间反转
时间复杂度O(n),最坏情况下遍历全部链表
空间复杂度O(1)

解法二:递归

  • 按照第一个递归缩短子问题找到反转起点
  • 按照第二个递归缩短终点的子问题,从第n个位置反转,向上拼接
class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode*tmp=NULL;
    ListNode*reverse(ListNode*head,int n)
    {
        if(n==1)//只颠倒第一个节点
        {
            tmp=head->next;
            return head;
        }
        //进入子问题求解
        ListNode*node=reverse(head->next,n-1);
        head->next->next=head;//反转
        head->next=tmp;//拼接
        return node;
    }
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        // write code here
        //非必要判断
        if(m==n||head==NULL||head->next==NULL)
            return head;
        if(m==1)//从第一个节点开始反转
            return reverse(head,n);
         //缩小子问题,我们只考虑p是反转后的链表的头就行
        ListNode*p=reverseBetween(head->next, m-1, n-1);
        //拼接已反转的部分
        head->next=p;
        return head;
    }
};

时间复杂度O(n),最坏遍历全部链表
空间复杂度O(n),递归栈深度最坏为n

三、 链表中的节点每k个一组翻转

解法一:查找反转

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseKGroup(ListNode* head, int k) {
        // write code here
        if(k<=1||head==NULL||head->next==NULL)
            return head;
        ListNode*node=head;
        int len=length(head);
        int part=len/k;//算出分为的组数
        //设置虚拟节点
        ListNode*result=new ListNode(-1);
        ListNode*cur=result;
        for(int i=0;i<part;i++)//外循环设置k个反转次数
        {
            ListNode*tmp=NULL;
            for(int j=0;j<k;j++)//每k个进行反转
            {
                ListNode*ret=head->next;
                head->next=tmp;
                tmp=head;
                head=ret;
            }
            cur->next=tmp;//拼接反转部分
            while(cur->next)
                cur=cur->next;//更新到尾节点的位置,为下一轮拼接做准备
            
        }
        //此时的头部已然为空
        cur->next=head;
        return result->next;
    }
    int length(ListNode*p)//计算节点个数
    {
        ListNode*cur=p;
        int count=0;
        while(cur)
        {
            count++;
            cur=cur->next;
        }
        return count;
    }
};

时间复杂度O(n),虽然使用双层循环,但最坏只是遍历了链表一遍
空间复杂度O(1)

解法二:递归(推荐)

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseKGroup(ListNode* head, int k) {
        // write code here
        ListNode*tail=head;//找到每次反转的尾部
        for(int i=0;i<k;i++)//遍历k次到尾部
        {
            if(tail==NULL)//到不了尾部直接返回
                return head;
            tail=tail->next;
        }
        ListNode*pre=NULL,*cur=head;//反转需要前序和当前节点
        while(cur!=tail)//在到达当前尾节点前循环反转
        {
            ListNode*tmp=cur->next;
            cur->next=pre;
            pre=cur;
            cur=tmp;
        }
        head->next=reverseKGroup(tail,k);//进行下一组的反转,同时实现拼接
        return pre;
    }
};

时间复杂度:O(n),一共遍历链表n个节点
空间复杂度:O(n),递归栈最大深度为n/k,也可看做O(1)

解法三:容器模拟

vector
这个就是把值存起来,反转每k个一组的数据,然后再重构

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseKGroup(ListNode* head, int k) {
        // write code here
        if(k<=1||head==NULL||head->next==NULL)
            return head;
        ListNode*cur=head;
        vector<int>v;
        int count=0,index=0;
        while(cur)
        {
            v.push_back(cur->val);
            count++;
            index++;
            if(count==k)
            {
                reverse(v.begin()+index-k,v.begin()+index);
                count=0;
            }
            cur=cur->next;
        }
        cur=head;
        for(int i=0;i<v.size();i++)
        {
            cur->val=v[i];
            cur=cur->next;
        }
        return head;
    }
};

stack
这个这是把节点存起来,利用栈的特点,边存边拼接重构

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseKGroup(ListNode* head, int k) {
        // write code here
        if(k<=1||head==NULL||head->next==NULL)
            return head;
        ListNode*p=new ListNode(-1);
        ListNode*cur=p;
        stack<ListNode*>s;
        int count=0;
        while(true)
        {
            for(int i=0;i<k;i++)
            {
                s.push(head);
                head=head->next;
                count++;
                if(head==NULL)
                    break;
            }
            if(count==k)
            {
                while(!s.empty())
                {
                    cur->next=s.top();
                    s.pop();
                    cur=cur->next;
                    cur->next=NULL;
                }
            }
            if(head==NULL)
                break;
            count=0;
        }
        ListNode*end=NULL;
        while(!s.empty())
        {
            end=s.top();
            s.pop();
        }
        cur->next=end;
        return p->next;
    }
};

时间复杂度O(n)
空间复杂度O(n)

这三道题逐步提高难度,在不使用额外空间的条件下,递归是最适合不过的了

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

...404 Not Found

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值