链表

50 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情

说实话,作为一个前端,在接触算法前是不知道链表是什么的。因为js里没有链表概念,只有数组。但是既然算法里有,我们就一起来探索下链表相关的算法吧。

链表是为了弥补数组的缺陷(必须开辟一段连续的内存空间)而产生的。

链表

  • 优点
    • 灵活分配内存空间
    • 只要位置前面的节点已知,可以在O(1)时间内删除或添加元素
  • 缺点
    • 查询元素需要O(n)的时间

数组

  • 优点
    • 根据下标查询元素需要O(1)的时间
  • 缺点
    • 构建时必须分配一段连续的内存空间
    • 在O(n)时间内删除或添加元素

链表的解题技巧:

  • 利用快慢指针
  • 构建一个虚拟的链表头

206. 反转链表

  • 我们需要构造一个虚拟的链表头pre,然后当前到的节点为cur,那么当cur移动到null时,pre正好是循环的最后一个元素,即为翻转后的链表头
var reverseList = function(head) {
    let pre = null
    let cur = head
    while (cur) {
        let temp = cur.next
        cur.next = pre
        pre = cur
        cur = temp
    }
    return pre
};

image.png

时间复杂度为O(n),空间复杂度为O(1)

92. 反转链表 II

  1. 先要找出左边界的上一个节点和右边界的下一个节点
  2. 然后切断中间需要反转的部分,反转中间链表
  3. 然后将反转后的链表与第一步找到的两个节点相连接
var reverseBetween = function(head, left, right) {
   let dummy = new ListNode(-1)
   dummy.next = head
   let leftPre, rightNext
   let pre = dummy
   // 1.
   for (let i = 0; i <= right; i++) {
       if (i < left) {
          leftPre = pre
       }
       if (i === right) {
           rightNext = pre.next
           break
       }
       pre = pre.next
   }
    
   // 2.
   let cur = leftPre.next
   leftPre.next = null
   pre.next = null
   reverseList(cur)

   // 3.
   leftPre.next = pre
   cur.next = rightNext
   return dummy.next
};

image.png

时间复杂度为O(n),空间复杂度为O(1)

  • 除此之外另一种方案不需要调用之前的反转列表
  • 整体思想为,在需反转区间内,每遍历到一个节点,就将它插入头部
var reverseBetween = function(head, left, right) {
    let dummy = new ListNode(-1)
    dummy.next = head
    let pre = dummy
    for (let i = 0; i < left - 1; i++) {
       pre = pre.next
    }
    let cur = pre.next
    for (let i = 0; i < right - left; i++) {
       let next = cur.next
       cur.next = next.next
       next.next = pre.next
       pre.next = next
    }
    return dummy.next
};

时间复杂度为O(n),空间复杂度为O(1)

其实链表题目,最主要是画图,会比较容易理出规则