算法通关村第2关|链表反转 青铜——终于学会链表反转了| Leetcode 206 反转链表(详细)

链表反转

反转链表涉及结点的增加、删除等多种操作,能非常有效考察思维能力和代码驾驭能力。另外很多题目也都要用它来做基础, 例如指定区间反转、链表K个一组翻转。还有一些在内部的某个过程用到了反转,例如两个链表生成相加链表。还有一种是链表排序的,也是需要移动元素之间的指针,难度与此差不多。因为太重要,所以我们用一章专门研究这个题目。

206. 反转链表:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

1. 建立虚拟头结点辅助反转

链表插入元素处理头结点是个比较麻烦的问题——>可以先建立一个虚拟的结点ans,并且令ans.next=head,这样可以很好的简化我们的操作。

如下图所示,如果我们将链表{1->2->3->4->5}进行反转,我们首先建立虚拟结点ans,并令ans.next=node(1),接下来我们每次从旧的链表拆下来一个结点接到ans后面,然后将其他线调整好就可以了。

在这里插入图片描述

// 方法1:虚拟结点法
public static ListNode reverseList(ListNode head) {\
	ListNode ans = new ListNode(-1);
	ListNode cur = head;
	while (cur != null) {
   		 ListNode next = cur.next;//先保存
		 cur.next = ans.next;
		 ans.next = cur;
		 cur = next;
}
         return ans.next;
}

2. 直接操作链表实现反转

观察一下反转前后的结构和指针位置:

在这里插入图片描述

再看一下执行期间的过程示意图,在图中:

  • cur本来指向旧链表的首结点
  • pre表示已经调整好的新链表的表头
  • next是下一个要调整的。

注意 图中箭头方向,cur和pre是两个表的表头,移动过程中cur经过一次中间状态之后,又重新变成了两个链表的表头。

在这里插入图片描述

class Solution {
    public ListNode reverseList(ListNode head) {
        //双指针
        ListNode pre=null;
        ListNode cur=head;
       
       //注意不是cur.next!=null
        while (cur != null) {
            //反转指针
            ListNode next=cur.next;//临时节点保存cur的下一个节点
            cur.next=pre;
            //移动指针
            pre=cur;
            cur=next;
        }
        return pre;
    }
}

3. 使用递归

递归的本质是将一个大问题分解为一个个子问题的解决过程

  1. 首先,定义递归的边界条件在本题中,当链表为空或只有一个节点时,无需反转,直接返回原链表头节点。
  2. 对于多于一个节点的情况,我们可以假设我们已经获得了反转后的子链表。那么我们只需要将当前节点的下一个节点指向当前节点,然后将当前节点的指针指向null,这样就实现了反转。
  3. 接下来,递归调用函数,传入当前节点的下一个节点,即 reverseList(head.next)。这样,递归的过程会将子链表反转,并获得反转后的子链表的头节点。
  4. 最后,我们将反转后的子链表的头节点返回,作为新的链表头节点。
class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(null,head);
    }

   private ListNode reverse(ListNode pre,ListNode cur){
       if (cur==null) {
           return pre;
       }
        ListNode temp=null;
        temp=cur.next;//先保存下一个节点
        cur.next=pre;//反转一个节点
        //依次递归
       return reverse(cur,temp);
   }
  --------------------------------------------------------------------------------  
      //递归第2种
    class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

  • 时间复杂度: O(n), 要递归处理链表的每个节点
  • 空间复杂度: O(n), 递归调用了 n 层栈空间

🎯如果将代码应用于链表1,2,3,4,5的反转

  • 在递归过程中,我们会从最后一个节点5开始逆序处理,即先将节点5的前一个节点的指针指向它,然后将节点5的指针指向null;
  • 接着,处理节点4的过程中,节点4的下一个节点是节点5,所以我们需要将节点5的指针指向节点4;
  • 接着,处理节点3的过程中,节点3的下一个节点是节点4,所以我们需要将节点4的指针指向节点3……以此类推
  • 最后处理完节点1的过程后,链表成功反转,新的链表头节点是节点5。

为什么cur一直指向5?img

我们可以在代码中看到,每次递归调用时,都是传入当前节点的下一个节点作为参数,即 reverseList(head.next),因此代码中的 cur 变量指向的是最后一个节点,也就是反转后的链表头节点。

具体来说,当递归逐步返回时,我们会将递归返回值设置为新的链表头节点,这个新的链表头节点会一直传递到递归开始的地方。当递归结束时,整个链表的头节点就是最后一次递归调用的返回值,也就是 cur = 5

因此,在这个具体的例子中,代码中的 cur 指向的是最后一个节点5,这是因为它是反转后的链表头节点

用的返回值,也就是 cur = 5

因此,在这个具体的例子中,代码中的 cur 指向的是最后一个节点5,这是因为它是反转后的链表头节点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值