返回

精解前端链表算法(上):翻转和分割合并

前端

引言

链表是一种广泛应用于前端开发中的数据结构,其结构简单、操作便捷,是解决各类问题的有力工具。理解链表算法对于前端工程师尤为重要,本文将深入探讨前端链表算法中的两大类型:翻转链表和分割合并,助力你攻克 LeetCode 链表难题。

翻转链表

翻转链表是指将链表中节点的顺序逆转,从链表尾部到头部依次排列。翻转链表算法的应用场景广泛,例如判断回文链表、合并两个排序链表等。

1. 迭代翻转

迭代翻转是翻转链表最常用的方法,其时间复杂度为 O(n),其中 n 为链表长度。算法步骤如下:

  • 初始化三个指针:prev 指向空节点(链表头结点的前一个节点)、curr 指向链表头结点、next 指向链表头结点的下一个节点。
  • 循环遍历链表,直到 curr 为空节点。
  • 在每次循环中,将 next 指向 curr 的下一个节点。
  • curr 指向 prev,即翻转指向。
  • prev 指向 currcurr 指向 next,指针后移。
  • 循环结束后,prev 指向翻转后的链表头结点。

代码实现:

const reverseListIterative = (head) => {
  let prev = null;
  let curr = head;
  let next;

  while (curr !== null) {
    next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
  }

  return prev;
};

2. 递归翻转

递归翻转也是翻转链表的一种常用方法,其时间复杂度同样为 O(n)。算法步骤如下:

  • 递归终止条件:当链表为空或只有一个节点时,直接返回。
  • 递归调用:将链表的剩余部分翻转,并返回翻转后的链表头结点。
  • 翻转当前节点:将当前节点的 next 指向递归返回的链表头结点,再将链表头结点指向当前节点。
  • 返回当前节点,即翻转后的链表头结点。

代码实现:

const reverseListRecursive = (head) => {
  if (head === null || head.next === null) {
    return head;
  }

  const newHead = reverseListRecursive(head.next);
  head.next.next = head;
  head.next = null;

  return newHead;
};

分割合并

分割合并是指将一个链表分割为两部分,再将分割后的两部分合并成一个新的链表。分割合并算法在解决链表排序、求中点等问题中十分有用。

1. 分割链表

分割链表的常见方法是快慢指针法,时间复杂度为 O(n)。算法步骤如下:

  • 初始化两个指针:slowfast,均指向链表头结点。
  • 循环遍历链表,直到 fast 指向链表尾部或 fast.next 指向链表尾部。
  • 此时,slow 指向链表前半部分的尾结点。将 slow.next 指向空节点,即分割链表。

代码实现:

const splitList = (head) => {
  if (head === null || head.next === null) {
    return [head, null];
  }

  let slow = head;
  let fast = head;

  while (fast !== null && fast.next !== null) {
    slow = slow.next;
    fast = fast.next.next;
  }

  const secondHead = slow.next;
  slow.next = null;

  return [head, secondHead];
};

2. 合并链表

合并两个有序链表的方法是逐个比较两个链表的头结点,时间复杂度为 O(n + m),其中 n 和 m 分别是两个链表的长度。算法步骤如下:

  • 初始化一个虚拟头结点 dummy,其 next 指向新链表的头结点。
  • 循环遍历两个链表,直到其中一个链表为空。
  • 在每次循环中,比较两个链表的头结点的大小,将较小的节点添加到新链表中。
  • 将当前新链表的尾结点的 next 指向另一个链表的头结点。
  • 返回 dummy.next,即新链表的头结点。

代码实现:

const mergeLists = (head1, head2) => {
  const dummy = new ListNode(null);
  let curr = dummy;

  while (head1 !== null && head2 !== null) {
    if (head1.val <= head2.val) {
      curr.next = head1;
      head1 = head1.next;
    } else {
      curr.next = head2;
      head2 = head2.next;
    }

    curr = curr.next;
  }

  curr.next = head1 !== null ? head1 : head2;

  return dummy.next;
};

结语

翻转链表和分割合并是前端链表算法中的两大重要类型,掌握这些算法对于解决各类链表问题至关重要。本文通过深入浅出的讲解和代码示例,帮助你理解算法的原理和实现,助你征服 LeetCode 链表难题,在前端开发中游刃有余。