分析下“反转单链表”递归算法

129 阅读1分钟
1. var reverse = function(head) {


    if (head === null || head.next === null) {
        return head;
    }
    var last = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return last;
};

 if (head === null || head.next === null) {
        return head;
    } 
head 指向链表第一个节点
递归终止的条件,只执行一次,head === null || head.next === null表示单链表最后一个节点



    var last = reverse(head.next);

    head.next.next = head;
    head.next = null;
    return last;
循环执行的逻辑

2.

var last = reverse(head.next);
这段是比较重点的,head 是当前的节点,先递归当前的节点的下一个节点,当前节点后续代码会延后执行
这里last 跟最后一句代码 return last;是对应的.可以看到last的值从未修改过,所以每层递归函数的
last都是一样。
先说下last是什么,last本来是指向最后一个节点,但是颠倒后就是第一个节点了,所以last 指向反转后
的链表


3.

前期代码执行的顺序是先递,

  if (head === null || head.next === null) {
        return head;
    }
    var last = reverse(head.next);

比如:链表 L =  [1 |] =>  [2 |] =>  [3 |] =>  [4 |] =>  [5 |] => null ,递的结果是

归是 从 reverse(5)开始

 if (head === null || head.next === null) {
        return head;
    }
    var last = reverse(head.next);

第5个节点的next 的值为 null
所以进入了

if分支,唯一执行一次这段代码,因为是递归终止的条件。
所以reverse(5return 5, 函数结束,并不会继续后续的代码

5.

reverse(4)

 if (4 === null || 4.next === null) {
        return 4;
    }
    var 5 = reverse(5);
是从这里开始执行的

head.next.next = head;
    head.next = null;
    return last; //这段返回的last 指针作为头指针,每个函数都是如此,所以不用再说了。


head.next.next = head;
    head.next = null;
主要的归代码 就是这两句。
head.next 指的是当前节点的下一个节点, 即 节点 [5 |]
head.next.next 就是下一个节点的next指针 即 5.next
head.next 当前节点的next 即 4.next

所以这两段代码的意思就是

二、反转链表前 N 个节点

需要3个指针

var successor = null;
function reverseN(head, n) {
    if (n == 1) {
        // 记录第 n + 1 个节点
        successor = head.next;
        return head;
    }
    // 以 head.next 为起点,需要反转前 n - 1 个节点
    var last = reverseN(head.next, n - 1);

    head.next.next = head;
    // 让反转之后的 head 节点和后面的节点连起来
    head.next = successor;
    return last;
}

当n == 1 时,已经是要反转的最后一个节点了。即递到了第n个元素,此时确定了successor

指针指向的节点,即第n + 1 个节点。然后返回 head 指针,head 指针指向的是当前的节点。

head 作为返回值 赋值 给n - 1 个节点 的last 变量。

var last = reverseN(head.next, n - 1);
...

return last;

这两段代码保证了后续的last 的值是不会改动,last 指向反转前的第n 个节点,

head.next.next = head;

这段代码也没有什么变化,作用还是一样的,下一个节点指向当前的节点。

head.next = successor;

唯一不同的就是这段代码,当前节点不是置空,而是指向successor指针,区别也不是很大,

这一步是为了保证代码执行到最后  反转前的第一个节点 指向 successor 。

不做这一步也没问题,因为真正需要这一步的就是最后一个归操作,但是n 无法确定,不知道最后一个递归的时机。