前端实现合并两个链表,通常指的是将两个已排序的链表合并成一个新的已排序链表。以下是一个使用 JavaScript 实现此功能的示例,并附带解释。
链表节点定义
首先,我们需要定义一个链表节点的结构。
js
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
合并两个链表函数
接下来,我们实现 mergeTwoLists
函数。这个函数会创建一个虚拟头节点(dummy head),然后遍历两个输入链表,将较小值的节点添加到新链表中。
js
/**
* 合并两个已排序的链表
* @param {ListNode} l1 第一个链表的头节点
* @param {ListNode} l2 第二个链表的头节点
* @return {ListNode} 合并后链表的头节点
*/
function mergeTwoLists(l1, l2) {
// 创建一个虚拟头节点,便于处理合并后链表的头部
const dummyHead = new ListNode(0);
let current = dummyHead; // current 指向当前构建的链表的最后一个节点
// 当两个链表都还有节点时,比较它们的值
while (l1 !== null && l2 !== null) {
if (l1.val <= l2.val) {
current.next = l1; // 将 l1 的当前节点添加到新链表
l1 = l1.next; // 移动 l1 到下一个节点
} else {
current.next = l2; // 将 l2 的当前节点添加到新链表
l2 = l2.next; // 移动 l2 到下一个节点
}
current = current.next; // 移动 current 到新添加的节点
}
// 如果其中一个链表还有剩余节点,直接将剩余部分连接到新链表
if (l1 !== null) {
current.next = l1;
} else if (l2 !== null) {
current.next = l2;
}
// 返回虚拟头节点的下一个节点,即合并后链表的真正头节点
return dummyHead.next;
}
示例用法
现在,我们来创建您提供的链表 a
和 b
,并使用 mergeTwoLists
函数进行合并。
js
// 辅助函数:从数组创建链表
function createLinkedList(arr) {
if (arr.length === 0) {
return null;
}
let head = new ListNode(arr[0]);
let current = head;
for (let i = 1; i < arr.length; i++) {
current.next = new ListNode(arr[i]);
current = current.next;
}
return head;
}
// 辅助函数:将链表转换为数组(方便打印和验证)
function linkedListToArray(head) {
const arr = [];
let current = head;
while (current !== null) {
arr.push(current.val);
current = current.next;
}
return arr;
}
// 创建链表 a: 1 -> 3 -> 5 -> 7 -> 9
const listA = createLinkedList([1, 3, 5, 7, 9]);
console.log("链表 a:", linkedListToArray(listA)); // 链表 a: [1, 3, 5, 7, 9]
// 创建链表 b: 2 -> 4 -> 6 -> 8 -> 10
const listB = createLinkedList([2, 4, 6, 8, 10]);
console.log("链表 b:", linkedListToArray(listB)); // 链表 b: [2, 4, 6, 8, 10]
// 合并链表 a 和 b
const mergedList = mergeTwoLists(listA, listB);
// 打印合并后的链表
console.log("合并后的链表:", linkedListToArray(mergedList)); // 合并后的链表: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
代码解释
-
ListNode
类:- 这是链表的基本组成单元。每个
ListNode
对象包含一个val
(节点的值)和一个next
(指向下一个节点的引用,如果当前是最后一个节点则为null
)。
- 这是链表的基本组成单元。每个
-
mergeTwoLists(l1, l2)
函数:-
虚拟头节点 (
dummyHead
) : 我们创建一个值为0
的虚拟头节点。它的作用是简化代码逻辑,尤其是在处理新链表的第一个节点时。最终,我们将返回dummyHead.next
,它才是合并后链表的真正头节点。 -
current
指针 :current
指针始终指向新合并链表的最后一个节点。通过移动current
,我们可以在新链表的末尾添加节点。 -
while (l1 !== null && l2 !== null)
循环:- 这个循环是合并过程的核心。它会一直运行,直到其中一个链表遍历完毕。
- 在每次迭代中,它比较
l1
和l2
当前节点的值。 - 如果
l1.val
小于或等于l2.val
,则将l1
的当前节点连接到current.next
,然后l1
移动到它的下一个节点。 - 否则,将
l2
的当前节点连接到current.next
,然后l2
移动到它的下一个节点。 - 无论哪种情况,
current
都会移动到刚刚添加的新节点,以便为下一个节点做好准备。
-
处理剩余节点:
- 当
while
循环结束时,意味着l1
或l2
中至少有一个已经到达末尾 (null
)。 - 如果
l1
还有剩余节点,说明l2
已经全部合并完毕,直接将l1
的剩余部分连接到current.next
。 - 同理,如果
l2
还有剩余节点,则将其连接到current.next
。由于两个输入链表都是已排序的,所以剩余的部分也必然是有序的,可以直接连接。
- 当
-
返回值 : 最后,函数返回
dummyHead.next
,这是合并后链表的实际头节点。
-
这个实现方法是合并两个已排序链表的经典且高效的算法,时间复杂度为 O(m+n),其中 m 和 n 分别是两个链表的长度,因为它只需要遍历每个链表一次。