返回
精解前端链表算法(上):翻转和分割合并
前端
2023-12-14 03:07:05
引言
链表是一种广泛应用于前端开发中的数据结构,其结构简单、操作便捷,是解决各类问题的有力工具。理解链表算法对于前端工程师尤为重要,本文将深入探讨前端链表算法中的两大类型:翻转链表和分割合并,助力你攻克 LeetCode 链表难题。
翻转链表
翻转链表是指将链表中节点的顺序逆转,从链表尾部到头部依次排列。翻转链表算法的应用场景广泛,例如判断回文链表、合并两个排序链表等。
1. 迭代翻转
迭代翻转是翻转链表最常用的方法,其时间复杂度为 O(n),其中 n 为链表长度。算法步骤如下:
- 初始化三个指针:
prev
指向空节点(链表头结点的前一个节点)、curr
指向链表头结点、next
指向链表头结点的下一个节点。 - 循环遍历链表,直到
curr
为空节点。 - 在每次循环中,将
next
指向curr
的下一个节点。 - 将
curr
指向prev
,即翻转指向。 - 将
prev
指向curr
,curr
指向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)。算法步骤如下:
- 初始化两个指针:
slow
和fast
,均指向链表头结点。 - 循环遍历链表,直到
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 链表难题,在前端开发中游刃有余。