happy
合并两个有序链表
先给答案:
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
const mergeTwoLists = function (l1, l2) {
if (l1 === null) {
return l2;
}
if (l2 === null) {
return l1;
}
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
再看题:
将两个升序链表,合并为,一个新的升序链表,并返回。
新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入: 1->2->4, 1->3->4
输出: 1->1->2->3->4->4
看关键点
:
- 要知道 链表数据结构
- 要考虑 边界这种情况
题目要求使用递归方法合并两个有序链表,基本思路
是:
1、比较两个链表头节点的值。
2、将较小的节点作为合并后链表的头节点。
3、递归地将该节点的next
指针指向剩余链表的合并结果。
4、当任一链表为空时终止递归。
看复杂度
:
M
、N
是两条链表l1
、l2
的长度
M、N 是两条链表 l1、l2 的⻓度
时间复杂度:O(M+N)
空间复杂度:O(M+N)
复杂度
分析:
时间:
- M 是链表 l1 的长度,N 是链表 l2的长度。
- 每次递归调用处理一个节点,总共需要处理
M+N
个节点。 - 每个节点的比较和连接操作都是常数时间 O(1)。
- 因此总时间复杂度与两个链表的总长度成正比。
空间:
- 主要来自递归调用栈的消耗。
- 最坏情况下(如两个链表完全交错),递归深度为
M+N
。 - 每次递归调用都会在调用栈中占用一个栈帧空间。
- 因此空间复杂度也与链表总长度成正比。
看迭代
:
var mergeTwoList = function (l1, l2) {
// 1、创建虚拟头节点
const prehead = new ListNode(-1);
// 2、维护一个prev指针指向当前合并链表的末尾
let prev = prehead;
// 3、循环比较两个链表的节点
while(l1 != null && l2 != null){
if (l1.val <= l2.val) {
prev.next = l1; // 将l1节点连接到结果链表
l1 = l1.next; // l1指针后移
} else {
prev.next = l2; // 将l2节点连接到结果链表
l2 = l2.next; // l2指针后移
}
prev = prev.next; // 结果链表指针后移
}
// 4、处理剩余节点
prev.next = l1 === null ? l2 : l1;
// 5、返回合并后的链表(跳过虚拟头节点)
return prehead.next;
}
步骤:
1、虚拟头节点:
- 创 一个 值 为
-1
的虚拟点作为合并链表的起始点。 - 简化了边界条件处理,无需单独处理空链表情况。
2、prev指针:
- 始终指向当前合并链表的最后一个节点。
- 用于连接下一个最小值的节点。
3、主循环:
- 比较l1和l2当前节点的值。
- 将较小值的及节点连接到
prev.next
。 - 移动较小值节点所在链表的指针。
- 移动prev指针到新连接的节点。
4、剩余节点处理:
- 当任一链表遍历完后,直接将另一链表的剩余部分连接到结果链表。
- 三元运算符判断哪个链表还有剩余节点。
5、返回结果:
- 返回
prehead.next
,跳过虚拟头节点。 - 得到的就是合并后的有序链表。
演示:
假设合并以下两个链表:
l1: 1 -> 3 -> 5
l2: 2 -> 4 -> 6
执行过程:
1、创建虚拟节点 - 1
2、第一次循环:1 < 2 -> 连接1,l1后移。
3、第二次循环:3 < 2 -> 连接2,l2后移。
4、第三次循环:3 < 4 -> 连接3,l1后移。
5、第四次循环:5 > 4 -> 连接4,l2后移。
6、第五次循环:5 < 6 -> 连接5,l1后移。
7、l1为null,循环结束。
8、连接l2剩余部分(6)。
9、最终结果:1 -> 2 -> 3 -> 4 -> 5 -> 6 。