【面试必备】算法基础之链表必会题

191 阅读10分钟

每晚22:00更新,不更新胖十斤


欢迎大家一起刷题~
题目均来自 牛客网 以及 LeetCode

1.反转链表(NC78、Leetcode#206)

  • 反转一个单链表

  • 示例:

    • 输入: 1->2->3->4->5->NULL
    • 输出: 5->4->3->2->1->NULL
  • 解答:

    • var reverseList = function(head) {
       if(!head||!head.next) return head
       var pre = null,cur = head
       while(cur){
        var temp = cur.next
        cur.next = pre 
        pre = cur
        cur = temp
       }
       head = pre
       return head
      };
      

2.两个链表的第一个公共节点(NC66、Leetcode剑指offer52、Leetcode#160)

  • 输入两个链表,找出它们的第一个公共节点

  • 分析:

    • 双指针法
      • 假设两个链表的长度相同,此时使用双指针(每个链表使用一个指针)从头到尾的比较,遇到第一个相同的节点则直接返回
      • 不管两个链表各自的长度是多少,只要两个链表相加,则会得到两个长度相同的链表,此时在使用双指针即可返回
      • 实现相加的方式即为从头到尾扫描链表,扫描到当前链表的最后一个节点时,指向另一个链表的头节点,从而达到相加的效果
    • 伪代码:
      • 1.分别保存两个链表的头指针
      • 2.循环判断两个头指针是否不相等,相等则退出循环
      • 3.循环内则进行双指针判断(每个指针进行比较是否为空,不为空则指向下一节点,为空则指向另一链表头节点)
  • 解答:

    • var getIntersectionNode = function(headA, headB) {
          var cur1 = headA,cur2 = headB
          while(cur1 != cur2){
              cur1 = cur1  ?  cur1.next : headB
              cur2 = cur2  ?  cur2.next : headA
          }
          return cur1
      };
      

3.合并两个有序链表(NC33)

  • 将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的

  • 分析:

    • 两个列表长度可能不同,所以只需要比较较短的链表和另一链表每个节点的大小
    • 循环比较,出现节点为null时则比较结束
    • 头节点此时需要判断那个链表的节点为空,则将头指向另一个链表的剩余部分
    • 返回新头节点
    • 伪代码:
      • 1.分别保存两个链表的头指针
      • 2.循环判断两个头指针是否不相等,相等则退出循环
      • 3.循环内则进行双指针判断(每个指针进行比较是否为空,不为空则指向下一节点,为空则指向另一链表头节点)
  • 解答:

    • /*
       * function ListNode(x){
       *   this.val = x;
       *   this.next = null;
       * }
       */
      
      /**
        * 
        * @param l1 ListNode类 
        * @param l2 ListNode类 
        * @return ListNode类
        */
      function mergeTwoLists( l1 ,  l2 ) {
          if(l1 == null || l2 == null) return l1 == null? l2 : l1
          //将每个链表的头节点的值进行保存
          var head1 = l1.val,head2 = l2.val
          //比较两头节点,取小的为新链表头节点
          var newHead = head1<=head2?l1:l2
          //新尾结点
          var tail = newHead
          //找到新头节点是哪个链表的头,找到的哪个指针后移,另一个则保持不变
          l1 = (newHead == l1)? l1.next:l1;
          l2 = (newHead == l2)? l2.next:l2;
          while(l1 != null && l2 != null){
              //比较较小的节点,将它插入新列表的尾
              if(l1.val<=l2.val){
                  tail.next = l1
                  l1 = l1.next
              }else{
                  tail.next = l2
                  l2 = l2.next
              }
              //插入完成后,尾后移
              tail = tail.next
          }
          //循环结束后一定有一个列表已经全部被比较完,此时指定新尾结点的指向
          //因为两个链表长度不一致,可能出现一个已经轮空,另一个还有节点的情况
          //此时,新链表尾节点应指向此时不为空的链表,因为每个链表都是有序的,所以不需要在对剩余节点进行排序
          tail.next = l1==null?l2:l1
          return newHead
      }
      module.exports = {
          mergeTwoLists : mergeTwoLists
      };
      

4.合并K个有序链表(NC51、Leetcode#23)

  • 合并 k 个已排序的链表并将其作为一个已排序的链表返回。分析并描述其复杂度。

  • 分析:

    • 合并两个有序链表 + 将给定K个列表进行遍历,两个两个调用合并两个有序链表
    • 伪代码:
      • 1.遍历lists,一个一个合并
      • 2.合并的核心方法还是双指针,与合并两个有序列表一样
  • 解答:

    • /**
       * Definition for singly-linked list.
       * function ListNode(val, next) {
       *     this.val = (val===undefined ? 0 : val)
       *     this.next = (next===undefined ? null : next)
       * }
       */
      /**
       * @param {ListNode[]} lists
       * @return {ListNode}
       */
      var mergeKLists = function(lists) {
          //最笨方法
          //1.遍历lists,一个一个合并
          //2.合并的核心方法还是双指针,与合并两个有序列表一样
          const mergeTwoLists = (l,r)=>{
              if(l==null||r==null) return l==null?r:l
              var head = l.val<=r.val?l:r
              var tail = head
              l = l==head ? l.next : l
              r = r==head ? r.next : r
              while(l&&r){
                  if(l.val<=r.val){
                      tail.next = l
                      l = l.next
                  }else{
                      tail.next = r
                      r = r.next
                  }
                  tail = tail.next
              }
              tail.next = !l ? r : l
              return head
          }
          var res = lists[0]
          if(lists.length==0) return null
          for (let i = 1;i<lists.length;i++){
              if(lists[i]){
                  res = mergeTwoLists(res,lists[i])
              }
          }
          return res 
      };
      

5.删除链表中的倒数第N个节点(NC53)

  • 给定一个链表,删除链表的倒数第n个节点并返回链表的头指针

    给出的链表为:1->2->3->4->5, n= 2.
    删除了链表的倒数第n个节点之后,链表变为1->2->3->5
    
  • 分析:

    • 找到删除的节点位置,并将这个指针指向他的下下个结点
    • 需要声明三个链表变量,第一个用来遍历得出链表长度,第二个用来进行删除操作,第三个用来返回结果,复制第二个链表
    • 伪代码:
      • 1.遍历一遍链表,得到链表长度
      • 2.根据数组长度循环得到删除的节点,循环长度为链表长度-删除的个数-1
      • 3.循环外进行断指针操作,将找到的删除节点进行删除操作
      • 4.返回
  • 解答:

    • function removeNthFromEnd( head ,  n ) {
          var new = head,tail = head,res = tail,length = 0
          //获取链表长度
          while(new!=null){
           length++
           new = new.next
          }
          if(length-n==0){
           //链表只有一个结点,直接删除,返回删除后的节点
           res = res.next
           return res
          }
          for(let i =0;i<length-n-1;i++){
           tail = tail.next//将指针移到需要删除的节点的前一个
          }
          tail.next = tail.next.next
          return res
      }
      module.exports = {
          removeNthFromEnd : removeNthFromEnd
      };
      

6.判断链表中是否有环(NC4)

  • 判断给定的链表中是否有环

  • 分析:

    • 快慢指针法
    • 声明两个指针,一个快一个慢,判断快指针,如果快指针直接null则肯定没有环
    • 伪代码:
      • 1.判断头节点,头的next,头的next.next有一个为空就不可能有环
      • 2.声明快慢指针
      • 3.循环快指针且快指针的下个结点都不为空,快慢指针一直移动,等到快慢指针如果相等,则有环
      • 4.返回
  • 解答:

    • /*
       * function ListNode(x){
       *   this.val = x;
       *   this.next = null;
       * }
       */
      
      /**
       * 
       * @param head ListNode类 
       * @return bool布尔型
       */
      function hasCycle( head ) {
          if(head==null||head.next==null||head.next.next==null)return false
          var slow = head,fast = head
          //必须判断快指针,如果快指针直接null则肯定没有环
          while(fast!=null&&fast.next!=null){
              fast = fast.next.next
              slow = slow.next
              if(fast==slow)return true
          }
          return false
      }
      module.exports = {
          hasCycle : hasCycle
      };
      

更新时间 2020-09-10 21:36

7.判断链表中是否有环(NC96)

  • 给定一个链表,请判断该链表是否为回文结构

  • 分析:

    • 思路为将链表每个节点的值保存至数组,数组前后比较是否相等,出现不等时,直接返回false
    • 伪代码:
      • 1.保存head节点cur,声明变量存放新建数组nodeArr=[],新建数组长度len,以及数组下标i
      • 2.循环判断cur不指向空,将链表节点值push进数组
      • 3.循环判断数组左侧下标小于右侧下标时,在判断左侧下标值是否等于右侧,不等于,返回false;i++
      • 4.否则返回true
  • 解答:

    /*
     * function ListNode(x){
     *   this.val = x;
     *   this.next = null;
     * }
     */
    
    /**
     * 
     * @param head ListNode类 the head
     * @return bool布尔型
     */
    function isPail( head ) {
        // write code here
        //前后两个指针,一个一个比对,一旦不相等 则return 
        var cur = head,nodeArr = []
        while(cur!=null){
            nodeArr.push(cur.val)
            cur = cur.next
        }
        var len = nodeArr.length,i = 0
        while(i<len-i-1){
            if(nodeArr[i]!==nodeArr[len-i-1]){
                return false
            }
            i++
        }
        return true
    }
    module.exports = {
        isPail : isPail
    };