算法通关村第二关——终于学会链表反转了

手写链表反转

1、问题背景

反转链表是一个出现频率特别高的算法题,在各大高频题排名网站也长期占领前三,比如长期占据牛客网的top1。所以链表反转是学习链表的过程中最最重要的问题,没有之一。

算法通关村第二关——终于学会链表反转了_第1张图片

2、问题描述

​ 给你一个单链表的头节点head,请你反转链表,并且返回反转之后的链表。

示例:

输入:head = [1,2,3,4,5]

输出:[5,4,3,2,1]

算法通关村第二关——终于学会链表反转了_第2张图片

3、解决方法

3.1 方法一:建立虚拟头结点辅助反转

​ 在链表插入元素的时候,如何去处理头结点是一个比较麻烦的问题,为此可以建立一个虚拟的节点ans,让ans指向链表的头结点,即ans.next = head,接下来每次从旧的链表上拆下来一个节点接到ans后面,再调整其他的线就好了。

算法通关村第二关——终于学会链表反转了_第3张图片

​ 从图上可以看到,每次从原链表中取下一个节点,接到虚拟头节点ans后面,即cur.next = ans.next,ans.next = cur,cur = next

为什么需要新用一个next来存储cur的下一个节点?

因为当cur被接入反转后的链表后,cur的next指针是指向ans的下一个节点的,如果不事先将next找到并存储起来,在接完cur后,next会丢失。

​ 具体的代码实现如下:

// 方法一:虚拟节点法
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;
}

3.2 方法二:直接操作链表实现反转

​ 上面一种方法虽然很好理解,应用也很广,但是比较常规,下面一种不借助虚拟节点的方法更能体现能力。

​ 先来看一下链表在反转前后的结构和指针的位置:

算法通关村第二关——终于学会链表反转了_第4张图片

​ 再来看一下执行期间的流程示意图,图中的cur本来指向旧链表的头节点,pre表示调整好的新链表的头节点,next是下一个需要调整的节点。

算法通关村第二关——终于学会链表反转了_第5张图片

注意图中箭头的方向,cur和prev原来是两个新旧链表的表头,在中间调整了cur的指针之后,需要平移一次prev和cur的位置,让其重新变成两个表的表头。

​ 以下是代码实现:

//方法二:直接操作链表
public static ListNode reverseList(ListNode head){
    ListNode prev = null;
    ListNode cur = head;
    while(cur != null){
        ListNode next = cur.next;
        cur.next = prev;
        prev = cur;
        cur = next;
    }
    return prev;
}

以上的代码可以在理解的基础上背下来,因为非常非常重要!!!!

3.3 方法三:通过递归实现链表反转

​ 先看代码,再解释一下递归的原理

//方法三:递归实现
public static 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;
}

​ 这个递归函数的工作原理是:首先检查链表是否为空或者只有一个节点。如果是,那么我们不需要做任何事情,所以我们直接返回头节点。否则我们递归地对剩余的链表调用这个函数,然后将当前的头节点设置为新的尾节点。最后,我们断开原来head的next,防止循环引用。

​ 具体图解如下:

算法通关村第二关——终于学会链表反转了_第6张图片

需要注意的是,递归解法虽然代码简洁,但是对于很长的链表,可能会导致调用栈深度超过系统限制,从而导致程序崩溃。这种情况下,一般使用其他的两种方法。

4、总结

​ 本文主要讨论了对于链表反转这一问题的解法,最常用的就是借助虚拟节点来实现,这在很多底层源码中都有使用,而面试中最常用的是第二种不带虚拟头结点的方法,这种解法需要重点理解和记忆,最后一种用递归来实现,代码会很简洁,思路也比较简单,但是由于递归本身所存在的缺点,这种解法也只做思路开拓。

你可能感兴趣的:(链表,算法,链表,数据结构)