每种编程语言都实现了数组,但在大多数语言中,数组大小是固定的(创建时指定),从数组起点或中间插入或移除元素的成本很高,因为后面的元素都需要挨个挪位置。

1. 链表数据结构

链表是一种动态的数据结构,用于存储有序的元素集合,可以从中任意添加或移除元素,不需要移动其他元素,可按需扩容(如众人手拉手,加个人和减个人都很简单),链表中的元素并不是连续放置的,每个元素由一个存储元素本身的节点,和一个指向下一个元素的引用(也称指针或链接)组成:
image.pngimage.png
数组可以直接访问任何位置的元素(可以理解为是物理电路,访问任意位置的元素都一样快),而想要访问链表中间的元素,就需要从表头开始迭代链表中的节点,挨个寻找,就像藏宝游戏,不断地根据线索寻找。
各自优势:

  • 链表:增、删
  • 数组:改、查

    1.1 创建链表

  • append(element):向列表尾部添加一个新的项。

  • insert(position, element):向列表的特定位置插入一个新的项。
  • remove(element):从列表中移除一项。
  • indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1。
  • removeAt(position):从列表的特定位置移除一项。
  • isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
  • size():返回链表包含的元素个数。与数组的length属性类似。
  • toString():由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值。

    1. function LinkedList() {
    2. // 单个节点类,表示要加入到链表中的项
    3. let Node = function (element) {
    4. this.element = element;
    5. this.next = null;
    6. };
    7. let length = 0; // 内部私有变量,记录链表长度
    8. let head = null; // 头指针
    9. /**
    10. * 向链表尾部添加一个新的项
    11. * 两种场景:链表为空,添加的是第一个元素;链表不为空,向其追加元素。
    12. *
    13. * @param element
    14. */
    15. this.append = function (element) {
    16. let node = new Node(element), current;
    17. if (head === null) {
    18. head = node;
    19. } else {
    20. current = head;
    21. // 从表头开始循环找到最后一项
    22. while (current.next !== null) {
    23. current = current.next;
    24. }
    25. // 把节点链接到最后一项的 next 上
    26. current.next = node;
    27. }
    28. length++; // 更新链表长度
    29. };
    30. /**
    31. * 从任意位置移除元素并返回被移除的元素
    32. * 两种场景:移除第一个元素;移除第一个以外的任一元素。
    33. * 被移除的元素被丢弃在计算机内存中,等待被垃圾回收器清理。
    34. *
    35. * @param {number} position
    36. * @returns {element|null}
    37. */
    38. this.removeAt = function (position) {
    39. // 检查是否越界
    40. if (position >= 0 && position < length) {
    41. let current = head,
    42. previous,
    43. index = 0;
    44. // 分两种情况:表头和非表头
    45. if (position === 0) {
    46. head = current.next;
    47. } else {
    48. // position 的前一个位置时终止循环
    49. while (index++ < position) {
    50. previous = current;
    51. current = current.next;
    52. }
    53. // 上下节点进行链接,跳过中间将被移除的 current 节点
    54. previous.next = current.next;
    55. }
    56. length--;
    57. return current.element;
    58. } else {
    59. return null;
    60. }
    61. };
    62. /**
    63. * 在任意位置插入元素
    64. *
    65. * @param {number} position
    66. * @param element
    67. * @returns {boolean}
    68. */
    69. this.insert = function (position = 0, element) {
    70. // 检查是否越界,注意这里包含了空链表时的情形
    71. if (position >= 0 && position <= length) {
    72. let node = new Node(element),
    73. current = head,
    74. previous,
    75. index = 0;
    76. if (position === 0) {
    77. node.next = current;
    78. head = node;
    79. } else {
    80. while (index++ < position) {
    81. previous = current;
    82. current = current.next;
    83. }
    84. node.next = current; // 即新节点插入到目标位置的前面,这里current可能是null
    85. previous.next = node;
    86. }
    87. length++;
    88. return true;
    89. } else {
    90. return false;
    91. }
    92. };
    93. /**
    94. * 把 LinkedList 对象转换成字符串
    95. *
    96. * @returns {string}
    97. */
    98. this.toString = function () {
    99. let current = head,
    100. string = '',
    101. index = 0;
    102. while (current) {
    103. string += index++ + ': ' + current.element + (current.next ? '\n' : '');
    104. current = current.next;
    105. }
    106. return string;
    107. };
    108. /**
    109. * 查找给定元素的索引,找不到则返回 -1
    110. *
    111. * @param element
    112. * @returns {number}
    113. */
    114. this.indexOf = function (element) {
    115. let current = head,
    116. index = -1;
    117. while (current) {
    118. index++;
    119. if (element === current.element) {
    120. return index;
    121. }
    122. current = current.next;
    123. }
    124. return -1;
    125. };
    126. /**
    127. * 移除给定的元素
    128. *
    129. * @param element
    130. * @returns {element|null}
    131. */
    132. this.remove = function (element) {
    133. const index = this.indexOf(element);
    134. return this.removeAt(index);
    135. };
    136. /**
    137. * 链表是否为空
    138. * @returns {boolean}
    139. */
    140. this.isEmpty = function () {
    141. return length === 0;
    142. };
    143. /**
    144. * 链表大小
    145. * @returns {number}
    146. */
    147. this.size = function () {
    148. return length;
    149. };
    150. /**
    151. * 获取表头
    152. * 方便实例外部访问和迭代链表
    153. *
    154. * @returns {element|null}
    155. */
    156. this.getHead = function () {
    157. return head;
    158. };
    159. }

    1.2 双向链表

    image.png

    1. function DoublyLinkedList() {
    2. let Node = function (element) {
    3. this.element = element;
    4. this.next = null;
    5. this.prev = null;
    6. };
    7. let length = 0;
    8. let head = null;
    9. let tail = null;
    10. /**
    11. * 从任意位置移除元素
    12. *
    13. * @param position
    14. * @returns {element|null}
    15. */
    16. this.removeAt = function (position) {
    17. if (position >= 0 && position < length) {
    18. let current = head,
    19. previous,
    20. index = 0;
    21. if (position === 0) { // 第一项
    22. head = current.next;
    23. // 如果只有一项,需要更新tail
    24. if (length === 1) {
    25. tail = null;
    26. } else {
    27. head.prev = null;
    28. }
    29. } else if (position === length - 1) { // 最后一项
    30. current = tail;
    31. tail = current.prev;
    32. tail.next = null;
    33. } else {
    34. while (index++ < position) {
    35. previous = current;
    36. current = current.next;
    37. }
    38. previous.next = current.next;
    39. current.next.prev = previous;
    40. }
    41. length--;
    42. return current.element;
    43. } else {
    44. return null;
    45. }
    46. };
    47. /**
    48. * 从任意位置添加节点
    49. *
    50. * @param {number} position
    51. * @param element
    52. * @returns {boolean}
    53. */
    54. this.insert = function (position, element) {
    55. if (position >= 0 && position <= length) {
    56. let node = new Node(element),
    57. current = head,
    58. previous,
    59. index = 0;
    60. if (position === 0) { // 在第一个位置添加
    61. if (!head) { // 当前链表为空
    62. head = node;
    63. tail = node;
    64. } else {
    65. node.next = current;
    66. current.prev = node;
    67. head = node;
    68. }
    69. } else if (position === length) { // 最后一项
    70. current = tail;
    71. current.next = node;
    72. node.prev = current;
    73. tail = node;
    74. } else {
    75. while (index++ < position) {
    76. previous = current;
    77. current = current.next;
    78. }
    79. previous.next = node;
    80. node.next = current;
    81. current.prev = node;
    82. node.prev = previous;
    83. }
    84. length++;
    85. return true;
    86. } else {
    87. return false;
    88. }
    89. };
    90. // 其他方法和单向链表类似
    91. }

    1.3 循环链表

    循环链表 CircularLinkedList 可以只有单向引用,也可以像双向链表一样有双向引用。
    单向循环链表中:循环链表中 tail.next 指向 head。
    image.png
    双向循环链表中:tail.next 指向 head,head.prev 指向 tail。
    image.png

    2. 链表相关问题

    2.1 判断链表是否存在环形

    ```javascript // https://leetcode.com/problems/linked-list-cycle/

// 1. 暴力解法(brute force): 迭代节点, 存储或进行标记, 如果遇见已经存在的记录, 则说明存在环形

// 2. 快慢双指针(龟兔赛跑): // slow速度为1, fast为2, 若fast遇见了slow(地球是圆的), 则说明存在环形

/**

  • Definition for singly-linked list.
  • function ListNode(val) {
  • this.val = val;
  • this.next = null;
  • } */

/**

  • 判断链表是否存在环形 *
  • @param {ListNode} head
  • @return {boolean} */ const hasCycle = function (head) { let slow = head, fast = head;

    while (fast !== null && fast.next !== null) { slow = slow.next; fast = fast.next.next; if (slow === fast) { return true; } } return false; }; ```

    2.2 求两个链表交集时的节点

    ```javascript // https://leetcode.com/problems/intersection-of-two-linked-lists/

// 1. 暴力解法(brute force): 使用数组或者set存储一条链表所有节点,然后迭代第二条链表进行查找是否存在(略)

// 2. 双指针 two pointers // 两个链表指针同时从各自表头移动, 速度一致, 当较短的链表(A)跑到头后, 较长的链表(B)就开始顺便重定向头部指针, // 此后B的前后两个指针之差始终等于A的总长度, // 然后等到B的后指针也跑到头后, 再同时从A和B的左侧指针开始迭代比较是否相等,找出交点.

// Time complexity : O(m+n)O(m+n). // Space complexity : O(1)O(1).

/**

  • Definition for singly-linked list.
  • function ListNode(val) {
  • this.val = val;
  • this.next = null;
  • } */

/**

  • 求两个链表交集时的节点 *
  • @param {ListNode} headA
  • @param {ListNode} headB
  • @return {ListNode|null} */ const getIntersectionNode = function (headA, headB) { let a = headA, b = headB;

    while (a !== null || b !== null) { if (a !== null) { a = a.next; } else { headB = headB.next; }

    if (b !== null) { b = b.next; } else { headA = headA.next; } }

    while (headA !== headB) { headA = headA.next; headB = headB.next; }

    return headA; }; ```