关于递归反转链表的思路

735 阅读5分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

前言

算法一直是考验程序员编程思想的一个重要点,一般来说中小型公司在面试中都不会特意去问这种问题,一般都是借着面试者的回答来往下问,比如涉及到Glide就会问LRUCache,HashMap就会问存储和计算Hash的算法.最近我也看了下LeetCode的一些题目,就来讲解一下自己的思路吧

Code

首先来定义一下实体类

public class ListNode {
    int val = 0;
    ListNode next;

    public ListNode(int data) {
        val = data;
    }
}

然后创建一个链表

public static void main(String[] args) {
    ArrayList<ListNode> listNodeList = new ArrayList();
    for (int i = 0; i < 5; i++) {
        ListNode listNode = new ListNode(i);
        listNodeList.add(listNode);
        if (i != 0 ) {
            listNodeList.get(i - 1).next = listNode;
        }
    }

    for (ListNode listNode : listNodeList
    ) {
        try {
            System.out.println(listNode.toString());
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }

}

image.png

然后就是反转细节

public static ListNode rever(ListNode head) {
    if (head.next == null) {
        return head;
    }
    ListNode rever = rever(head.next);
    head.next.next = head;
    head.next = null;
    return rever;
}

之前看一篇微信公众号的文章说自己不要代入递归,人的脑袋压不了几个栈,我觉得不应该这么说,即使数据无限大我们也应该压几个方法在脑袋里过一下,把整个流程和细节简单的形成一个印象,下面我用画图的方式来讲解一下我的思路

对了,递归的一个特性就是回溯

比如我们第一个rever方法传递的参数是集合中第一个数据,也就是说他的基础val是0,next的ListNode的val是1;如下图

反转2.png

不要看太后面,主要看蓝色标注部分就行,现在只要记住传递的数据就是那么多

if (head.next == null) {
    return head;
}

因为链表的特性就是一个节点链接着另一个节点,上面代码就是判断当前节点是不是还有下一个节点,没有就直接返回了,这是递归的一个出口,不设置就无限递归直接报错了

ListNode rever = rever(head.next);

这一句就比较重要的,递归的起始点,将我们一开始传递进来的参数headnext作为参数再次调用自身.

我们一开始传递的ListNodeval是0,他的next的ListNode的val肯定是1.就是上图我蓝色标注的.所以说这个地方的参数ListNodeval = 1.

走到这个地方之后就会进入新的方法,此时的我们rever的参数head他的val是1了,next指向的ListNodeval是2了.

再次判断传递进来的参数的next是不是null,不是就继续往下走,再次走到

ListNode rever = rever(head.next);

再次进入新的方法,此时方法rever的参数headval是2了,next指向的ListNode是null.这里就压3个栈,压多了会越看越迷糊.

判断后直接return head,也就是直接将当前参数返回过去了.

重点!!!此时递归出口已经触发,开始回溯,我现在将压的栈简单的写在下面

第一个栈

//head 的 val = 0,next 指向的 ListNode的 val = 1
public static ListNode rever(ListNode head) {
    if (head.next == null) {
        return head;
    }
    ListNode rever = rever(head.next);
    head.next.next = head;
    head.next = null;
    return rever;
}

第二个栈

//head 的 val = 1,next 指向的 ListNode 的 val = 2
public static ListNode rever(ListNode head) {
    if (head.next == null) {
        return head;
    }
    ListNode rever = rever(head.next);
    head.next.next = head;
    head.next = null;
    return rever;
}

第三个栈

//head 的 val = 2,next 指向的 ListNode = null 直接返回
public static ListNode rever(ListNode head) {
    if (head.next == null) {
        return head;
    }
    ListNode rever = rever(head.next);
    head.next.next = head;
    head.next = null;
    return rever;
}

就先压三个,理解一下运行流程

此时第三个栈参数headnext指向的ListNode是null,所以直接返回到第二个栈中,因为递归的回溯吗,要回去当时调用你的方法,就相当于搭积木

image.png

第三个栈不符合要求,直接return了,我们就相当于消费了这个方法,就需要回到第二个栈中.此时注意这个一行

ListNode rever = rever(head.next);

这个时候已经来到了我们的第二个栈了,第二个栈的参数headval = 1,next 指向的 ListNodeval = 2,所以返回的就是我们传递的next数据:val = 2,next = null

然后继续向下执行

head.next.next = head;
head.next = null;
return rever;

已知headnext指向的ListNode为: val = 2,next = null

经过上面代码第一行的洗礼我们的ListNode发生了变化: val = 2,next = head

现在数据变成了这个样子

image.png

但是我们要的可不是双向链表,所以执行第二行

head.next = null;

然后数据就变成了这样

image.png 你成功反转了一个节点,此时我们将目光向第三行看齐

return rever;

这一句将我们反转后的数据return了,也代表了第二个栈的消亡.

此时来到了第一个栈,return的ListNode数据为val = 2,next指向的ListNode就是我们上图:val = 1,next = null.

我们接着看代码

ListNode rever = rever(head.next);
head.next.next = head;
head.next = null;
return rever;

当前rever数据就是我刚刚说的,我们开始走第二行.

head.next.next = head;

记住,这是第一个栈了,我们当时说得,第一个栈的数据是ListNode中val = 0,next指向的ListNode中val = 1,因为第二个栈中的反转,现在指向的ListNode为null了;

head.next等于下面这个

image.png

head.next.next等于这个

image.png

也就是说,将这个null重新变为我们的第一个数据

image.png

然后老套路,不能双向啊,给他置空

head.next = null;

image.png 然后

return rever;

三个节点的链表就给我们这么处理完了,来打印一下吧

image.png

好啦,这就是我对于递归反转链表的思路,第一次看可能很懵,head.next.next,别着急,我第一次看别人博客,文章的时候也是这样

好啦就这么多,有点头疼,磨蹭磨蹭就睡觉了,明天还有好多工作呢,┏(^0^)┛