JavaScript数据结构 + leetcode - 链表

125 阅读4分钟

本文总结leetcode上关于链表的一些高频题,方便以后深入理解学习。

概念

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

题目

难度:简单

206. 反转链表

剑指 Offer 24. 反转链表

代码展示:

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let p1 = head;
    let p2 = null;
    while (p1) {
        const next = p1.next;
        p1.next = p2;
        p2 = p1;
        p1 = next;
    }
    return p2;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

21. 合并两个有序链表

剑指 Offer 25. 合并两个排序的链表

代码展示:

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var mergeTwoLists = function(l1, l2) {
    const l3 = new ListNode(0);
    let p1 = l1;
    let p2 = l2;
    let p3 = l3;
    while (p1 || p2) {
        if (!p1) { p3.next = p2; break; }
        if (!p2) { p3.next = p1; break; }
        const r = p1.val > p2.val;
        if (r) {
            p3.next = new ListNode(p2.val);
            p2 = p2.next;
        } else {
            p3.next = new ListNode(p1.val);
            p1 = p1.next;
        }
    }
    p3 = p3.next;
    return l3.next;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

剑指 Offer 22. 链表中倒数第k个节点

面试题 02.02. 返回倒数第 k 个节点

代码展示:

/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var getKthFromEnd = function(head, k) {
    let p1 = head;
    let p2 = head;
    while (k > 0) {
        p2 = p2.next;
        k--;
    }
    while (p1 && p2) {
        p1 = p1.next;
        p2 = p2.next;
    }
    return p1;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

141. 环形链表

代码展示:

var hasCycle = function(head) {
    let p1 = head;
    let p2 = head;
    while (p1 && p2 && p2.next) {
        p1 = p1.next;
        p2 = p2.next.next;
        if (p1 === p2) return true;
    }
    return false;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

83. 删除排序链表中的重复元素

代码展示:

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
  let p = head;
  while (p && p.next) {
    if (p.val === p.next.val) {
      p.next = p.next.next;
    } else {
      p = p.next;
    }
  }
  return head;
};

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

234. 回文链表

代码展示:

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var isPalindrome = function(head) {
    const vals = [];
    let p1 = head;
    while (p1) {
        vals.push(p1.val);
        p1 = p1.next;
    }
    console.log(vals);
    for (let i = 0, j = vals.length - 1; i < vals.length, j >= 0; i++, j--) {
        if (vals[i] !== vals[j]) {
            return false;
        }
    }
    return true;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(n)

剑指 Offer 06. 从尾到头打印链表

代码展示:

/**
 * @param {ListNode} head
 * @return {number[]}
 */
var reversePrint = function(head) {
    let p1 = head;
    const res = [];
    while (p1) { 
        res.push(p1.val);
        p1 = p1.next;
    }
    return res.reverse();
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(n)

160. 相交链表

代码展示:

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
  if (!headA || !headB) return false;
  let p1 = headA;
  let p2 = headB;
  while (p1 !== p2) {
    p1 = p1 ? p1.next : headB;
    p2 = p2 ? p2.next : headA;
  }
  return p1;
};

复杂度分析:

  • 时间复杂度:O(m+n):m为A链表节点数,n为B链表节点数。
  • 空间复杂度:O(1)

面试题 02.03. 删除中间节点

剑指 Offer 52. 两个链表的第一个公共节点

代码展示:

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    let p1 = headA;
    let p2 = headB;
    while (p1 != p2) {
        p1 = p1 ? p1.next : headB;
        p2 = p2 ? p2.next : headA;
    }
    return p1;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(n)

203. 移除链表元素

代码展示:

 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function(head, val) {
    if (!head) return null;
    while (head.val === val) {
        head = head.next;
        if (!head) return head;
    }
    let p1 = head;
    let p2 = p1.next;
    while (p2) {
        if (p2.val === val) {
            p1.next = p2.next;
            p2 = p1.next;
        } else {
            p1 = p1.next;
            p2 = p2.next;
        }
    }
    return head;
};

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

剑指 Offer 18. 删除链表的节点

代码展示:

/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var deleteNode = function(head, val) {
    if (head.val === val) return head.next;
    let p1 = head;
    let p2 = p1.next;
    while (p2) {
        if (p2.val === val) {
            p1.next = p2.next;
        }
        p1 = p1.next;
        p2 = p2.next;
    }
    return head;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

237. 删除链表中的节点

代码展示:

/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
  node.val = node.next.val;
  node.next = node.next.next; 
};

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

难度:中等

2. 两数相加

代码展示:

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
  const l3 = new ListNode(0);
  let p1 = l1;
  let p2 = l2;
  let p3 = l3;
  let addOne = 0;
  while(p1 || p2) {
    let v1 = p1 ? p1.val : 0;
    let v2 = p2 ? p2.val : 0;
    const sum = v1 + v2 + addOne;
    addOne = ~~(sum / 10);
    p3.next = new ListNode(sum % 10);
    if (p1) p1 = p1.next;
    if (p2) p2 = p2.next;
    p3 = p3.next;
  }
  if (addOne) p3.next = new ListNode(addOne);
  return l3.next;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

92. 反转链表 II

代码展示:

/**
 * @param {ListNode} head
 * @param {number} left
 * @param {number} right
 * @return {ListNode}
 */
var reverseBetween = function(head, left, right) {
    let p1 = new ListNode(-1);
    p1.next = head;
    let p2 = p1;
    for (let i = 0; i < left - 1; i++) {
        p2 = p2.next;
    }
    let cur = p2.next;
    for (let i = 0; i < right - left; i++) {
        const next = cur.next;
        cur.next = next.next;
        next.next = p2.next;
        p2.next = next;
    }
    return p1.next;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

143. 重排链表

代码展示:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {void} Do not return anything, modify head in-place instead.
 */
var reorderList = function(head) {
    if (!head) return head;
    const temp = [];
    while (head) {
        temp.push(head);
        head = head.next;
    }
    let i = 0; j = temp.length - 1;
    while (i < j) {
        temp[i].next = temp[j];
        i++;
        if (i === j) break;
        temp[j].next = temp[i];
        j--;
    }
    temp[i].next = null;
    return temp[0];
};

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

82. 删除排序链表中的重复元素 II

代码展示:

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
    if (!head) return head;
    const l1 = new ListNode(0, head);
    let p1 = l1;
    while (p1.next && p1.next.next) {
        if (p1.next.val === p1.next.next.val) {
            const v = p1.next.val;
            while (p1.next && p1.next.val === v) {
                p1.next = p1.next.next;
            }
        } else {
            p1 = p1.next;
        }
    }
    return l1.next;
}

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

19. 删除链表的倒数第 N 个结点

代码展示:

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    if (!head) return head;
    const l1 = new ListNode(0, head);
    let p1 = l1;
    let p2 = p1;
    while (p2) {
        p2 = p2.next;
        n--;
    }
    while (p1 && p2) {
        if (!p2.next) {
            p1.next = p1.next ? p1.next.next : null;
        }
        p1 = p1.next;
        p2 = p2.next;
    }
    return l1.next;
};

复杂度分析:

  • 时间复杂度:O(n):n为链表节点数。
  • 空间复杂度:O(1)

148. 排序链表

思路:

  • 利用归并排序,先将链表对半拆分,一直拆分到单个链表节点

  • 然后通过merge方法将每个独立链表边排序边合并,最终输出一个排好序的链表

代码展示

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var sortList = function (head) {
  if (!head || !head.next) return head;
  let slow = head, fast = head;
  let preSlow = null;
  while (fast && fast.next) {
    preSlow = slow;  // preSlow可以保存前一半链表
    slow = slow.next; // slow的速度是fast的一半
    fast = fast.next.next; // 当fast为null时,slow刚好可以获取到后一半链表
  }
  preSlow.next = null; // 此时preSlow.next = null,将链表截断,只剩前半条链表
  const l = sortList(head);  // 此时输入的head就是前一半链表,并且递归一直拆分成独立元素
  const r = sortList(slow);  // slow为后一半链表,递归拆分成独立元素
  return merge(l, r);  // 拆分到最小单元后,进行从短到长链表的合并
}

var merge = function (l1, l2) {
  const dummy = new ListNode(-1);
  let p0 = dummy;
  let p1 = l1;
  let p2 = l2;
  while (p1 && p2) {
    if (p1.val < p2.val) {
      p0.next = p1;
      p1 = p1.next;
    } else {
      p0.next = p2;
      p2 = p2.next;
    }
    p0 = p0.next;
  }
  if (p1) p0.next = p1;
  if (p2) p0.next = p2;
  return dummy.next;
}

复杂度分析:

  • 时间复杂度:O(nlogn)

  • 空间复杂度:O(logn)

面试题 02.05. 链表求和

86. 分隔链表

61. 旋转链表

142. 环形链表 II

445. 两数相加 II

147. 对链表进行插入排序

328. 奇偶链表

138. 复制带随机指针的链表

24. 两两交换链表中的节点

难度:困难

25. K 个一组翻转链表

23. 合并K个升序链表

总结:

链表数据结构对于我来说有点不太好操作,需要多多练习,加深理解。