[链表] 206. 反转链表 92. 反转链表 II(反转整个链表、前n个节点、第m个~第n个节点:迭代、递归实现)

206. 反转链表(反转整个链表)

题目链接:https://leetcode-cn.com/problems/reverse-linked-list/

相似题目:92. 反转链表 II(反转部分链表)

分类:链表(反转整个链表:迭代实现、递归实现)


在这里插入图片描述

思路1:迭代实现

设置一个节点post,作为反转后节点.next指向的对象,初始时post为null,这是因为链表头结点在反转后会变为尾结点,而又要求尾结点.next=null,所以在链表的第一个节点反转之后,该节点变为反转后链表的尾结点,所以令该节点的next域 = post。

拿 post 覆盖当前节点的next域之前,要先将节点的原next域备份为temp,以便当前节点处理完后能找到原链表下的下一个节点继续做反转:

ListNode temp = head.next;

然后拿post 覆盖当前节点的next域:

head.next = post;

拿post更新当前节点的next域之后,就要更新 post,为下一轮反转做准备:
因为下一轮要反转的节点是原链表下head的下一个节点,也就是之前备份的temp,原来是head → temp,反转后变为temp → head,因为 post 记录的就是每一轮反转后节点的next域所指向的对象,此时下一轮要反转的temp的next指向这一轮的head,所以在这一轮先把 head 备份到 post 上:

 post = head;

最后在进入下一轮反转之前,取出之前备份的 temp 覆盖 head,相当于取出原链表的下一个节点作为工作节点,准备做下一轮反转,直到head == null :

head = temp;

(算法流程讲起来很乱,看代码自己模拟一遍过程就懂了)

实现代码

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode post = null;
        while(head != null){
            ListNode temp = head.next;//备份当前节点的原next域
            head.next = post;//更新当前节点反转后的next域
            post = head;//将当前节点作为post供下一个节点的next域使用
            head = temp;//更新head
        }
        return post;
    }
}

思路2:递归实现(更难理解,更巧妙)

参考链接:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/

反转递归实现的要点:用内部栈来保存head。

例如:1->2->3->4->5->NULL

每进入一层递归就是取链表的下一个节点,深入到最深层时,head=5,head.next=null,记录链表的尾结点post=head=5,然后把该尾结点post返回给上一层。

上一层对应的 head=4,head.next=5,则head.next.next=head表示5->4,相当于把4->5反转为5->4。然后仍然返回post=5,相当于把5->4视为反转后的新链表,把反转后的头结点返回给上一层了。

其他节点以此类推,反转链表在递归一层层向上返回的过程中不断扩展,最终得到反转后的完整链表。

  • 注意:每一层得到的反转链表都要确保是有头有尾的链表,避免节点和旧链表有联系造成未知环路,所以每有一个新节点加入到反转链表的末尾,就将它的next置为null,避免出现环路:
    例如:上例中得到5->4后,需要再令4.next=null,得到当前构造中的反转链表:5->4->null;
    继续返回上一层,此时的head=3,head.next=4,head.next.next=head,相当于5->4->3,再置3->null,得到5->4->3->null。
    以此类推。

  • 增加了这一步也解决了最终反转链表的尾结点的next域置null的处理。

递归返回值
这里递归返回值的作用比较独特。

每一层递归都能从内部栈里得到当前层对应的head,因为最终要返回的是反转后链表的头结点,也就是原链表的尾结点,所以每一层递归返回给上一层的都是最深层递归得到的尾结点post。

递归的返回值并没有参与反转,而是始终指向原链表的尾结点,作为反转后链表的头结点,以便最后的返回。

实现代码

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head.next == null) return head;
        ListNode post = reverseList(head.next);//递归最深层的head=4,post=5
        head.next.next = head;
        head.next = null;//不影响中间节点的反转,但可以让头结点反转变为尾结点后置next域为null,让反转链表变得完整
        return post;
    }
}

92. 反转链表 II(反转部分链表)

题目链接:https://leetcode-cn.com/problems/reverse-linked-list-ii/

分类:链表(反转部分链表:迭代实现、递归实现)

在这里插入图片描述

题目分析:(很好的递归练习题)

本题的递归实现更难,也更巧妙,可以作为对递归很好的训练题目。

相似题目:206. 反转链表,206反转的是整个链表,当本题的m=1,n=链表长度,问题就退化为206题。

反转部分链表和反转整个链表的区别在于:反转完部分链表后,还需要将反转后这部分插回原链表中,所以需要记录反转部分的前一个节点和后一个节点,以便回插。

思路1:一趟扫描 + 迭代实现

特殊用例:m=n表示待反转链表只有一个点,返回原链表即可。

设置虚拟头结点dummy,设置一个指针start,用于指向第m个节点的前一个节点,目的在于当m~n的节点反转结束后,要将反转后的链表插入回原链表,需要这个指针和工作指针head来标记反转链表的边界。

同时设置一个指针post, 表示反转后节点.next指向的对象,具体作用和206题的post相同。

算法流程

设置一个计数器count,初始时count=1,start指向dummy,post指向head:

//三个指针的初始状态
dummy->1->2->3->4->5->6->NULL,m=2,n=5
  ↑    ↑
start post/head  

当count < m时,start,post,head同步后移,每移动一位,count+1;

当count == m时,说明找到待反转部分的第一个节点,start不再移动,此时start指向的就是待反转链表的前一个节点。开始执行反转操作:此时head指向待反转部分的第一个节点,post = head。因为待反转链表的最后一个节点的next目前是未知的,不像206题已知为null,所以post初始化时随意赋一个值。

一个节点的反转操作:

    备份节点的原next域:
    ListNode temp = head.next;
    head.next = post;
    post = head;
    head = temp;

至此一个节点反转完毕,count+1.

当count==n时,说明待反转链表内部已经反转完毕,此时的链表状态为:

//待反转部分反转完毕后是三个指针的状态
dummy->1 5->4->3->2 6->NULL,m=2,n=5
       ↑ ↑          ↑
    start post    head

剩下的就是将反转后的链表插入原链表中:

  • 注意:此时start.next=2节点,即start.next指向的是反转后反转部分的最后一个节点。
	ListNode tail = start.next;
	start.next = post;
	tail.next = head;

得到:

//将反转后部分插入原链表中:
dummy->1->5->4->3->2->6->NULL,m=2,n=5
       ↑  ↑        ↑  ↑
     start post    tail head

反转完毕。

实现代码

class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        //特殊用例
        if(m == n) return head;

        //虚拟头结点
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode start = dummy, post = head;
        int count = 1;//计数器
        //反转m~n的节点
        while(count <= n){
        	//寻找第m个节点
            if(count < m){
                start = start.next;
                post = post.next;
                head = head.next;
            }
            //count>=m,开始反转操作
            else{
                ListNode temp = head.next;
                head.next = post;
                post = head;
                head = temp;
            }
            count++;
        }
        //将反转后的链表插入原链表中
        ListNode tail = start.next;
        start.next = post;
        tail.next = head;
        return dummy.next;
    }
}

思路2:一次扫描 + 递归实现(基于 反转链表前n个节点)

参考:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/

首先,回顾206.反转整个链表的递归实现思路;

接着,先学习反转前n个节点的递归实现:

和反转整个链表相比,反转前n个节点,首部的处理不变,变的是反转部分尾结点的处理。在反转整个链表的题目中,原链表的头结点反转后变为最后一个节点,所以它的next要指向Null,但反转前n个节点的题目中,原链表的头节点反转后,它的next 要指向 第 n + 1 个节点。也就是说,第n个节点的反转需要特殊处理。

而且反转后链表的头结点也是 原链表的第 n 个节点,所以递归返回值仍然是反转后链表的头结点,也就是原链表的第 n 个节点。

那么,如何寻找第 n 个节点?

递归函数的参数给定了 题目要求的n,即反转前n个节点。我们可以利用这个参数,每进入一层递归 n - 1,当 n == 1时,当前层递归的head指向的就是原链表的第n个节点。

例如:
1->2->3->4->5->6->NULL , n=5
↑
head ,n=5

每进入一层递归,head = head.next,n-- ... 

1->2->3->4->5->6->NULL , n=5
            ↑
     head ,n=1

实现代码

class Solution {
	ListNode tail = null;//记录第n+1个节点
    public ListNode reversePre(ListNode head, int n) {
        if(n == 1){
        	tail = head.next;//把第n+1个节点赋给tail
        	return head;
        }
        ListNode post = reverseList(head.next, n - 1);//每进入一层递归,head = head.next,n--
        //反转操作和206相同
        head.next.next = head;
        head.next = tail;//不影响中间节点的反转,但可以让头结点反转变为尾结点后置next域为tail,让反转部分插入原链表中
        return post;
    }
}

最后,扩展到反转m~n个节点的方法。

  • 当m == 1时,问题就退化为反转前n个节点。

  • 当m > 1时,从 head 开始找反转起点第m个节点,进入下一层递归后变为从head.next出发寻找第m-1个节点,以此类推,当m == 1时,又退化到反转前n个节点的问题。
    */

class Solution {
    ListNode post = null;
    public ListNode reverseBetween(ListNode head, int m, int n) {
        //特殊用例
        if(m == n) return head;
        if(m == 1) return reversePreN(head, n); 

        head.next = reverseBetween(head.next, m - 1, n - 1);
        return head;
    }
    //递归反转链表前n个节点
    public ListNode reversePreN(ListNode head, int n){
        if(n == 1){
            post = head.next;
            return head;
        }
        ListNode newHead = reversePreN(head.next, n - 1);
        head.next.next = head;
        head.next = post;
        return newHead;
    }
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值