链表内指定区间反转

链表内指定区间反转

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题解 文章被收录于专栏

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

全部评论

相关推荐

10-18 13:01
已编辑
西安理工大学 C++
小米内推大使:建议技能还是放上面吧,hr和技术面试官第一眼想看的应该是技能点和他们岗位是否匹配
点赞 评论 收藏
分享
10-24 11:10
山西大学 Java
若梦难了:哥们,面试挂是很正常的。我大中厂终面挂,加起来快10次了,继续努力吧。
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务