算法:反转链表(JavaScript版)

        这道题在力扣(LeetCode)上有两道题不同难度的题,下面由易到难梳理一下逻辑。

题一:简单难度。

        众所周知,js是没有指针的概念的,但是有引用类型的object,跟指针类似,也是访问的内存地址,所以js里一般用object来模拟指针,为了方便理解,后文统一按指针来讲。

        这道题如果了解链表的概念的话,思路其实很简单:

                改变一下每一项的指针指向

                将第一项的指针指向null

                其余每一项的指针都指向前一个节点。

        比较简单,直接看代码。

var reverseList = function(head) {
    let next = null;    // 临时变量,存储每个节点的指向
    let newHead = null;    // 临时变量,存储前一个节点
    while(head) {    // 当前节点为空时退出循环
        next = head.next;    // 将下一个节点的指向存下来
        head.next = newHead;    // 将下一个节点指向前一个节点

        newHead = head;    // 将当前节点变为前一个节点
        head = next;    // 将下一个节点变为当前节点,开始新一轮的循环
    }
    return newHead;    // 最后,输出前一个节点,就会倒着将所有节点输出了
};

题二:进阶版的反转链表,中等难度。

         当我们学会了第一道反转链表的方法,再看到这道题就简单很多了。大致思路如下。

                首先将链表分为三部分,中间待转区域为一部分,两边不需要反转的区域各为一部分。

                然后将待转区域的链表采用上文题1的方法进行反转。

                最后再将两边的区域和反转过后的链表拼接起来即可。

        思路比较简单,不过本题的难点是如何对链表进行分割,直接看代码,注释比较清楚:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} left
 * @param {number} right
 * @return {ListNode}
 */
var reverseBetween = function(head, left, right) {
    let newHead = new ListNode(-1); // 首先创建一个虚拟节点,因为头节点在处理过程中可能发生变化,使用虚拟头节点为了存一下头节点的位置
    newHead.next  = head;   // 虚拟节点下一个节点的指针指向链表头节点

    let pre = newHead;  // pre从头节点出发,循环到left - 1之前的位置
    for(let i = 0; i < left-1; i++) {   // 循环结束后,pre位于反转区域的前一区域的最后一个节点位置
        pre = pre.next;
    }

    let net = pre;  // net 从反转区域最左边节点出发,循环到反转区域最右边节点位置
    for(let i = left - 1; i < right; i++) {   // 循环结束后,net位于反转区域的最后一个节点位置
        net = net.next;
    }

    let two = pre.next; // pre的下一个节点则是反转区域的头节点
    let three = net.next;   // net的下一个节点则是反转区域的后一区域的头节点,将这个节点位置存储下来,后面拼接时需要使用

    net.next = null;    // 将net的下一个节点位置置为null,截断反转区域和第三区域的联系
    pre.next = null;    // 将pre的下一个节点位置置为null,截断第一区域和反转区域的联系

    reverseList(two);   // 调用题一的方法,进行链表反转

    // 将三部分区域链表重新拼接
    pre.next = net; // net最后的位置是反转区域的最后一个位置,反转过后则为反转区域的第一个位置,故第一区域指向该节点
    two.next = three;

    return newHead.next;    // 返回之前虚拟节点存储的链表头节点,系统则会依次输出
};

        这种方法思路比较简单,也容易实现,不过有一个缺点就是,当left和right正好是链表两端的值时,需要遍历两遍链表才能实现该算法。感兴趣的可以看一下下面的方法二,如何通过一次遍历就实现该算法的。

        方法二的基本思路是,头插法:

        想象一下,当我们在日常开发中,一下子接到4个需求,默认文档里排序是4,3,2,1,对于有强迫症的我们,就想把他们重新排一下顺序。

                于是我们从第二项开始,将每一项跟第一项比较

                发现3比4优先级高,排到4前面,结果为3,4,2,1。

                再往后看,2比3高,排到3前面,结果为2,3,4,1。

                再往后看,1比2高,排到2前面,结果为1,2,3,4。

        这就是所谓的头插法(别问为什么不用Excel什么的,它只是个栗子)。

        这道题我们也可以采用这种方法来解,上代码:

 * }
 */
/**
 * @param {ListNode} head
 * @param {number} left
 * @param {number} right
 * @return {ListNode}
 */
var reverseBetween = function(head, left, right) {

    let newHead = new ListNode(-1); // 创建虚拟节点
    newHead.next  = head;   // 指向头节点

    let pre = newHead;  // 从头节点开始,我们需要循环到需要反转的区域开始位置
    for(let i = 0; i < left - 1; i++) { // 循环结束,pre指向反转区域的第一个节点
        pre = pre.next;
    }

    let curr = pre.next;   // curr表示待反转区域的第一个节点的位置,每次插一个数据,curr会往后移动一个位置(curr可以理解为当前要处理的节点)
    for(let i = left; i < right; i++) { // 需要反转的区域有几个节点,我们就插n-1次(从反转区域第二个节点开始)
        let next = curr.next;   // next表示curr的下一个节点(这里是将当前节点的下一个节点存下来)
        curr.next = next.next;  // 当前节点的下一个节点指向下一个节点的下一个节点(其实就是往后移动一个位置,为了下一次循环)
        next.next = pre.next;   // 当前节点的下一个节点的下一个节点指向反转区域的第一个节点。(这里可能不好理解了,可以结合下面配图来看)
        pre.next = next;    // 最后再把反转区域的第一个节点修改为当前节点的下一个节点(四步下来,实现了调换)
    }

    return newHead.next;
};

       

考虑到这种方法,可能有点绕,所以这里对循环增加一点配图讲解

第19行代码:

 第21行代码:

 第22行代码:

 第23行代码:

  第24行代码:

 循环到第21行代码:

后面重复的操作,不再赘述。

        虽然了解链表的基本概念,但是却没有用js实现过链表的算法,这次也算是一次尝试。还有就是链表类型的题,建议还是要在纸上画一遍的,能更好的理解它。哪里有不对的地方,欢迎指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值