《剑指Offer:专项突破版》 - 链表部分 JavaScript 题解

Posted 前端GoGoGo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《剑指Offer:专项突破版》 - 链表部分 JavaScript 题解相关的知识,希望对你有一定的参考价值。

《剑指Offer:专项突破版》是一个算法题集。该题单包含了程序员在准备面试过程中必备的数据结构与算法知识。具体包含:

  • 数据结构:整数、数组、字符串、链表、哈希表、栈、队列、树、堆和前缀树。
  • 算法:二分查找、排序、回溯法、动态规划和图搜索。
  • 本文来分享下链表部分题的解法~

    类似。可以用类似的算法来求解:

    1. 反转两个链表。
    2. 将反转后的链表的每位依次相加。直到达到长的链的尾部。如果当前位已超过了短链的长度,则短链当前位的值为0。用长链来存求和的结果。
    3. 如果长链的最高位进位了,则在链的尾部添加值为1的节点。
    4. 反转长链。

    代码如下:

    [4, 5, 6]
  • 反转后半部分。后半部分变为:[6, 5, 4]
  • 反转后的后半部分的节点,一隔一的嵌入前部分的节点。变为:[1, 6, 2, 5, 3, 4]
  • 将链表按长度平均分成两部分,可以用快慢指针来实现。快指针一次走两步,慢指针一次走一步。快指针走到链表末尾时,慢指针指向的就是中间节点。

    完整的实现代码如下:

    const reorderList = function (head
      if (!head || !head.next || !head.next.next) 
        return head;
      
      let  prevHalf, afterHalf  = divideToHalf(head);
      afterHalf = reverseList(afterHalf);
      while (prevHalf) 
        const prevHalfNext = prevHalf.next;
        prevHalf.next = afterHalf;
        if (afterHalf) 
          // 奇数到末尾的情况
          const afterHalfNext = afterHalf.next;
          afterHalf.next = prevHalfNext;
          afterHalf = afterHalfNext;
        
        prevHalf = prevHalfNext;
      
      return head;
    ;

    // 链表长度至少为3。
    function divideToHalf(head
      let fast = head;
      let slow = head;
      let middle = head;
      while (fast && fast.next) 
        fast = fast.next.next;
        middle = slow;
        slow = slow.next;
      
      const isOdd = !!fast;
      let afterHalf;
      if (isOdd) 
        // 奇数
        afterHalf = slow.next;
        slow.next = null;
       else 
        afterHalf = middle.next;
        middle.next = null;
      
      return 
        prevHalf: head,
        afterHalf,
      ;

    题7 - 剑指 Offer II 027. 回文链表

    问题:如何判断一个链表是不是回文?要求解法的时间复杂度是O(n),并且不得使用超过O(1)的辅助空间。如果一个链表是回文,那么链表的节点序列从前往后看和从后往前看是相同的。

    题的力扣地址[8]

    回文链表的特点是:将链表一分为二,后半部分反转后和前半部分的节点的值都相同。因此,不难设计出如下的算法:

    1. 将链表一分二。如果链表长度是奇数个,去掉中间节点。
    2. 反转后半部分链表,然后比较前后两部分是否相等。

    核心代码如下:

    const isPalindrome = (head) => 
      if (!head || !head.next) 
        return true;
      
      let  prevHalf, afterHalf  = divideToHalf(head);
      afterHalf = reverseList(afterHalf);
      while (prevHalf) 
        if (prevHalf.val !== afterHalf.val) 
          return false;
        
        prevHalf = prevHalf.next;
        afterHalf = afterHalf.next;
      
      return true;
    ;
    题8 - 剑指 Offer II 028. 展平多级双向链表

    问题:在一个多级双向链表中,节点除了有两个指针分别指向前后两个节点,还有一个指针指向它的子链表,并且子链表也是一个双向链表,它的节点也有指向子链表的指针。请将这样的多级双向链表展平成普通的双向链表,即所有节点都没有子链表。

    题的力扣地址[9]

    这题看起来很难,其实用递归的方式来做很容易。算法如下:

    1. 遍历链表,遇到有子链表的,把该节点的下一个节点丢入栈中。
    2. 递归调用,将 子链表展平 后的节点,设置为父节点的下一个节点。
    3. 遍历完链表,取出栈中的节点。递归调用,将 节点展平 后的节点,添加到链表的末尾。
    4. 重复上一步,值到栈为空。程序结束。

    代码如下:

    const flatten = function (head
      if (!head) 
        return head;
      
      const stack = [];
      let currNode = head;
      let lastNode = null;
      while (currNode) 
        if (currNode.child) 
          stack.push(currNode.next);
          currNode.next = flatten(currNode.child);
          currNode.child.prev = currNode;
          currNode.child = null;
        
        lastNode = currNode;
        currNode = currNode.next;
      
      while (stack.length) 
        const node = stack.pop();
        if (node) 
          lastNode.next = flatten(node);
          node.prev = lastNode;
        
      
      return head;
    ;
    题9 - 剑指 Offer II 029. 排序的循环链表

    问题:在一个循环链表中节点的值递增排序,请设计一个算法在该循环链表中插入节点,并保证插入节点之后的循环链表仍然是排序的。

    题的力扣地址[10]

    算法如下:

    1. 遍历一圈,找到环中值最大的节点,最大值和最小值。
    2. 如果插入值大于等于最大值或小于等于最小值。则该节点插入到最大值和最小值节点中间。
    3. 插入值处于最大值,最小值中间时,从最小节点开始遍历,找到要插入的节点。

    该题包含的情况挺多的,要注意下。比如:

  • 循环列表没有节点。
  • 循环列表只有一个节点。
  • 循环列表只有两个节点。
  • 循环列表的第一个节点不是最小值: [3, 4, 1]
  • 循环列表的第一个节点是最小值: [1, 3, 4]
  • 插入的值比最大值大或比最小值小。
  • 完整代码如下:

    const insert = function (head, insertVal
      const insertNode = new Node(insertVal);
      // 没有节点的情况
      if (!head) 
        insertNode.next = insertNode;
        return insertNode;
      
      // 一个节点的情况
      if (head.next === head) 
        insertNode.next = head;
        head.next = insertNode;
        return head;
      
      // 两个节点的情况
      if (head.next.next === head) 
        if (insertVal > head.next.val || insertVal < head.val) 
          insertNode.next = head;
          head.next.next = insertNode;
         else 
          let temp = head.next;
          head.next = insertNode;
          insertNode.next = temp;
        

        return head;
      

      let max = Number.MIN_VALUE;
      let min = Number.MAX_VALUE;
      const firstNode = head;
      let beforeNode = firstNode;
      let currNode = beforeNode.next;
      let nextNode = currNode.next;
      let maxNode;
      let beforeMaxNode;
      // 找最大值和最小值
      while (currNode.next !== firstNode.next) 
        const currMax = Math.max(currNode.val, nextNode.val);
        const currMin = Math.min(currNode.val, nextNode.val);
        // 可能会存在多个最大值相同的情况。不加等号会出问题。
        if (currMax >= max) 
          max = currMax;
          beforeMaxNode = currNode.val >= nextNode.val ? beforeNode : currNode;
          maxNode = beforeMaxNode.next;
        
        if (currMin < min) 
          min = currMin;
        
        beforeNode = beforeNode.next;
        currNode = currNode.next;
        nextNode = nextNode.next;
      

      // 插入值大于等于最大值或小于等于最小值。则该节点插入到最大值和最小值节点中间。
      const isTooBigOrSmall = insertVal >= max || insertVal <= min;
      if (isTooBigOrSmall) 
        const beforeInsertNode = maxNode;
        insertNode.next = beforeInsertNode.next;
        beforeInsertNode.next = insertNode;
        return head;
      

      // 从最小节点转一圈,一定找得到插入的点
      currNode = maxNode.next;
      nextNode = currNode.next;
      while (currNode.next !== maxNode.next) 
        if (currNode.val <= insertVal && nextNode.val >= insertVal) 
          currNode.next = insertNode;
          insertNode.next = nextNode;
          return head;
        
        currNode = currNode.next;
        nextNode = nextNode.next;
      
    ;
    总结

    链表中经常会用到快慢指针的算法。可以用快慢指针的算法:

  • 来探测链表中是否有环。
  • 计算环的长度。
  • 将链表一分为二。当然,也支持将链表任意等分。只需改快指针的速度即可。
  • 相关阅读
  • 《剑指Offer:专项突破版》 - 数组部分 javascript 题解
  • 《剑指Offer:专项突破版》 - 字符串部分 JavaScript 题解
  • 《剑指Offer:专项突破版》 - 整数部分 JavaScript 题解
  • 参考资料
    [1]

    题的力扣地址: https://leetcode-cn.com/problems/SLwz0R/

    [2]

    题的力扣地址: https://leetcode-cn.com/problems/c32eOV/

    [3]

    题的力扣地址: https://leetcode-cn.com/problems/3u1WK4/

    [4]

    题的力扣地址: https://leetcode-cn.com/problems/UHnkqh/

    [5]

    题的力扣地址: https://leetcode-cn.com/problems/lMSNwu/

    [6]

    剑指 Offer II 002. 二进制加法: https://leetcode-cn.com/problems/JFETK5/

    [7]

    题的力扣地址: https://leetcode-cn.com/problems/LGjMqU/

    [8]

    题的力扣地址: https://leetcode-cn.com/problems/aMhZSa/

    [9]

    题的力扣地址: https://leetcode-cn.com/problems/Qv1Da2/

    [10]

    题的力扣地址: https://leetcode-cn.com/problems/4ueAj6/

    《剑指Offer:专项突破版》 - 哈希表部分 JavaScript 题解

    《剑指Offer:专项突破版》是一个算法题集。该题单包含了程序员在准备面试过程中必备的数据结构与算法知识。具体包含:

  • 数据结构:整数、数组、字符串、链表、哈希表、栈、队列、树、堆和前缀树。
  • 算法:二分查找、排序、回溯法、动态规划和图搜索。
  • 本文来分享下哈希表部分题的解法~

    和 最大值做比较,比如:["00: 00", "23:49", "23:59"] 的最小时间差不是 10 分钟(23:59 - 23:49),而是 1 分钟(00: 00 + 24:00 - 23:59)。

    代码如下:

    const ONE_DAY_MINUTE = 24 * 60
    const findMinDifference = function(timePoints
      if(timePoints.length <= 1
        return 0;
      
      const minutes = timePoints.map(time => toMinute(time));
      minutes.sort((a, b) => a - b);
      const diff = minutes.map((m, i) => 
        if(i === 0)  // 最后一个和第一个的差值。
          return m + ONE_DAY_MINUTE - minutes[minutes.length - 1];
        
        return m - minutes[i - 1];
      )
      const min = Math.min(...diff);
      return min;
    ;

    function toMinute(time
      const [hour, minute] = time.split(\':\').map(Number);
      return hour * 60 + minute;

    相关阅读
  • 《剑指Offer:专项突破版》 - 链表部分 JavaScript 题解
  • 《剑指Offer:专项突破版》 - 数组部分 JavaScript 题解
  • 《剑指Offer:专项突破版》 - 字符串部分 JavaScript 题解
  • 《剑指Offer:专项突破版》 - 整数部分 JavaScript 题解
  • 参考资料
    [1]

    题的力扣地址: https://leetcode-cn.com/problems/FortPu/

    [2]

    题的力扣地址: https://leetcode-cn.com/problems/OrIXps/

    [3]

    题的力扣地址: https://leetcode-cn.com/problems/dKk3P7/

    [4]

    题的力扣地址: https://leetcode-cn.com/problems/sfvd7V/

    [5]

    题的力扣地址: https://leetcode-cn.com/problems/lwyVBB/

    [6]

    题的力扣地址: https://leetcode-cn.com/problems/569nqc/

    以上是关于《剑指Offer:专项突破版》 - 链表部分 JavaScript 题解的主要内容,如果未能解决你的问题,请参考以下文章

    《剑指Offer:专项突破版》 - 哈希表部分 JavaScript 题解

    《剑指Offer:专项突破版》 - 整数部分 JavaScript 题解

    《剑指Offer:专项突破版》 - 栈部分 JavaScript 题解

    剑指offer第二版和专项突击版有啥区别

    剑指offer第二版和专项突击版有啥区别

    剑指Offer[Python版]