链表内指定区间反转

链表内指定区间反转

http://www.nowcoder.com/questionTerminal/b58434e200a648c589ca2063f1faf58c

在此之前,我们可以来看比较简单的反转链表:
图片说明


有两种方法: 迭代法和递归法:
  • 迭代法:
    相当于看成是两个大节点,一个是head,一个是ReverseList

    public class Solution {
    
      public ListNode ReverseList(ListNode head) {
          if(head == null) return null;
          if(head.next == null) return head;
          ListNode pre = null;
          ListNode next = null;
          while(head != null){
              next = head.next;
              head.next = pre;
              pre = head;
              head = next;
          }
          return pre;
      }
    }
  • 递归法:
    可以将整个链表看成只有head节点和reverseList节点,然后进行反转。返回的是反序后的头结点。
    图片说明

    public class Solution {
    
      public ListNode ReverseList(ListNode head) {
          if(head == null) return null;
          if(head.next == null) return head;
          ListNode last = ReverseList(head.next); //三步  last节点为reverseList
          head.next.next = head; //反转last节点
          head.next = null; //反转head节点
          return last; //返回last节点
      }
    }

这道题是上面的扩展:
我们也可用迭代法和递归法:

迭代法比较好理解:

public class Solution {
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    public ListNode reverseBetween (ListNode head, int m, int n) {

        ListNode pre = null;
        ListNode post = null;    //分别保存子链的前一个节点和后一个节点
        ListNode begin = null;
        ListNode end = null;    //分别保存子链的第一个节点和最后一个节点
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode p = dummy;    //从头结点开始
        int cnt = 0;
        while(cnt <= n){
            if(cnt+1 == m){    //找到子链的前一节点和第一节点
                pre = p;
                begin = p.next;
            }
            if(cnt == n){    //找到子链的后一节点和最后节点
                end = p;
                post = p.next;
            }
            ++cnt; 
            p = p.next;

        }

        ListNode q = begin;
        p = begin.next;
        while(p != post){    //开始反转
            ListNode next = p.next;    //保存反转目标节点的下一个节点
            p.next = q;
            q = p;
            p = next;
        }
        //重新合链
        pre.next = end;
        begin.next = post;
        return dummy.next;

    }
}

递归法:

public class Solution {
    /**
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // base case
        if(m == 1){
            return reverseN(head,n);
        }
        //递归到反转的起点,触发反转的起点 base case
        head.next = reverseBetween(head.next, m-1, n-1);
        return head;
    }
    ListNode post = null;
    /**
    *    反转以head为起点的n个节点,返回新的头结点
    */
    public ListNode reverseN(ListNode head, int n){
        if(n == 1){
            //记录第n+1个节点,后面合链时要用
            post = head.next;
            return head;
        }
        ListNode last = reverseN(head.next, n-1);
        head.next.next = head;
        head.next = post;
        return last;
    }
}

这两者的时间复杂度都是O(N),而迭代的空间复杂度为O(1),递归的是O(N),因为需要堆栈。
所以建议用迭代算法


再看一道题: 反转每k个为一组的链表 同样是多定义几个节点来保存上下文信息,实现反序后的合链操作。

图片说明


public class Solution {
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
        if(head == null || head.next == null)
                return head;

        //定义哑节点
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        //初始化pre和end都指向dummy。pre指每次要反转的链表的头结点的上一个节点。end指的是每次要反转的链表的尾结点
        ListNode pre = dummy;
        ListNode end = dummy;
        while(end.next != null){
            //循环k次,找到需要反转的链表的结尾
            //每次循环都要end判空,因为如果为空,end.next会报空指针异常
            for(int i=0; i < k && end != null; i++){
               end = end.next;
            }
            //如果end为空,即需要反转的链表节点数小于k,不执行反转
            if(end == null)
                break;

            //先记录下end.next,方便后面链接链表
            ListNode next = end.next;
            //然后断开链表
            end.next = null;
            //记录要反转链表的头结点
            ListNode start = pre.next;
            //反转链表,pre.next指向反转后的链表
            pre.next = reverse(start);
            //反转后头结点变到最后,通过.next把断开的聊表重新链接
            start.next = next;
            //将pre换成下次要反转的链表的头结点的上一个节点,即start
            pre = start;
            //将end置为下次要反转的链表的头结点的上一个节点
            end = start;
        }
        return dummy.next; 
    }

    public ListNode reverse(ListNode head){
        if(head == null || head.next == null){
            return head;
        }
        //前一个节点指针
        ListNode preNode = null;
        //当前节点指针
        ListNode curNode = head;
        //下一个节点指针
        ListNode nextNode = null;
        while(curNode != null){
            nextNode = curNode.next; //nextNode 指向下一个节点,保存当前节点后面的链表
            curNode.next = preNode; //将当前节点next指向前一个节点
            preNode = curNode; //preNode向后移动 preNode指向当前节点
            curNode = nextNode; //curNode指针向后移动, 下一个节点变成当前节点
        }
        return preNode;
    }
}
剑指Offer题解 文章被收录于专栏

为了之后反复练习方便查阅。

全部评论

相关推荐

xdm&nbsp;早上喝奶茶差点喷出来。事情是这样的,我们班有个哥们儿,简称&nbsp;L,去年秋招拿了字节sp,专业方向是后端。我们当时都震惊:这哥们儿平时课上从来不发言,期末小组作业基本是划水的那种,刷题平台&nbsp;commit记录我点进去看过,绿格子稀稀拉拉。但他面试一路绿灯。一面二面三面&nbsp;hr&nbsp;面,全过,给的还是sp。当时班级群里恭喜他的、问他经验的、约饭的,热闹了一周。他说自己"运气好,准备充分"。我们都信了,直到三月初他入职。入职第二周开始,班里另一个进字节的同学W(在隔壁组的)开始跟我他的不对劲。一开始是写代码慢,后来写不出来,再后来是组里&nbsp;mentor&nbsp;让他fix&nbsp;一个简单&nbsp;bug&nbsp;都搞了一下午没动静。最离谱的是上周。W&nbsp;说他们大部门搞了个新人分享会,让新人讲一下自己负责模块的设计思路。L&nbsp;上去讲了&nbsp;20分钟,全程念稿子,问答环节别人随便问一个"那你这里为什么用&nbsp;Redis&nbsp;不用&nbsp;Memcached",他直接卡&nbsp;30秒说"这个我回去再确认一下"。会后他&nbsp;mentor&nbsp;直接找&nbsp;leader&nbsp;谈,leader&nbsp;找&nbsp;hr&nbsp;谈,hr调出了他面试录像,全程对比口型和回答节奏,发现他二三面有大量时长在偷偷看屏幕外(推测开了双机位&nbsp;AI&nbsp;答题)。(这段是&nbsp;W后来转述给我的,他自己也是听他组里同事八卦来的)昨天下班前,W&nbsp;告诉我L&nbsp;被辞退了,让他自己走,不走就走仲裁但会发函到学校。L&nbsp;现在已经回学校了,朋友圈仅三天可见。我说真的,我不是个心眼小的人,但是我看到这个消息的时候真的有种"嗯,挺好"的感觉。去年秋招我投字节后端,简历挂。我准备了八个月,背&nbsp;八股&nbsp;+&nbsp;刷&nbsp;500&nbsp;题&nbsp;+项目改了三版,连面试机会都没拿到。班里这哥们儿凭着一个外挂上岸,最后还是被甩出来了。不是说作弊就一定会被发现,但是当面试拿到的&nbsp;offer远远超出真实能力的时候,迟早会有这一天。试用期三个月不是给你过家家的,是真的要写代码、要在会议上回答问题、要扛需求的。我现在反而有点同情他。同情他相信"上岸就是终点"。发出来不是为了嘲笑谁,就是想说给那些正在被身边作弊上岸的同学搞得很&nbsp;emo&nbsp;的&nbsp;uu&nbsp;们听——别急,回旋镖很长,但它一定会回来。你继续刷你的题,写你的项目,背你的八股。该是你的迟早是你的,不是你的早晚还得还回去。xdm&nbsp;共勉。
牛客12588360...:我不想评论面试方式,作弊是绝对不对的,但是你八股加刷题也不过是个做题小子,他穿帮纯粹是他菜,你也没有高明到哪里去
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务