反转单链表的几种方式对比(包括双指针法和递归)

需求:

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

方式一:双指针法

建立一个虚拟节点

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode pre = null; //建立一个虚拟节点
        ListNode cur = head;
        ListNode temp;
        while(cur != null){
           temp = cur.next;
           cur.next = pre;
           pre = cur;
           cur = temp;
        }
        return pre;
    }
}

方式一的错误示例:不建立虚拟节点

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode pre = head; //唯一改动的地方
        ListNode cur = head.next;
        ListNode temp;
        while(cur != null){
           temp = cur.next;
           cur.next = pre;
           pre = cur;
           cur = temp;
        }
        return pre;
    }
}

错误原因分析:

/**
形成的新链表中
5结点的下一个结点更改为4结点(在题设给的链表中,5结点的下一个结点本为null)
4.next = 3; (题设链表中 4.next = 5)
3.next = 2;
2.next = 1;
若按照上述代码提交 新链表的1.next = 2,仍然指向2结点,故形成了环。

根本原因在于:一开始的时候pre = head; cur = head.next;这样的话pre.next就一直指向cur,就算反转完之后也存在这个指针,所以就在前两个节点之间形成了死循环。
*/

方式二:递归

递归第一种写法:

class Solution {
    //反转整个链表
    public ListNode reverseList(ListNode head) {
        return reverse(null,head);
    }
    
    //反转指定节点curr,并把反转后的节点返回
    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);
    }
}

递归第二种写法(注意这里的head是一个虚拟节点):

//用来反转整个链表
    public void reverse(){

        //判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
        if (isEmpty()){
            return;
        }

        reverse(head.next);
    }

    //反转指定的结点curr,并把反转后的结点返回
    public Node reverse(Node curr){
        if (curr.next==null){
            head.next=curr;
            return curr;
        }
        //递归的反转当前结点curr的下一个结点;返回值就是链表反转后,当前结点的上一个结点
        Node pre = reverse(curr.next);
        //让返回的结点的下一个结点变为当前结点curr;
        pre.next=curr;
        //把当前结点的下一个结点变为null
        curr.next=null;
        return curr;
    }

上面两种递归写法对比:

上面两种递归方式可以类比如下情景:在电影院有一排排座位,只有第一排知道自己的座位号(对应在链表中头结点,也就是递归的起始条件,注意这里所说的头结点指的是虚拟节点),现在的需求是每一排都需要知道自己的座位号。

第一种递归方式就是从第一个开始,依次告诉后面一排自己的座位号,每一排座位都在前面一排的座位号加一(递归),到最后一排的时候把自己的座位号返回(递归中的return)。那这样每一排就可以知道自己的座位号了。

第二种方式也是从第一排开始,但是只是告诉后面一排要算座位号(递归)(注意并没有告诉自己的座位号,所以后面一排也没有计算自己的座位号),直到最后一排的时候就把第一排的座位号传给最后一排,最后一排带着座位号返回的时候(递归的return),这样返回的过程中每一排也就知道了自己的座位号。

简而言之:第一种方式是在往下递归的时候就开始计算自己的座位号,第二种方式是回来的计算自己的座位号。

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