# 21.合并两个有序链表
TIP
标签:递归、链表
将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表所有节点组成的。
示例1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
1
2
2
示例2:
输入:l1 = [], l2 = []
输出:[]
1
2
2
示例3:
输入:l1 = [], l2 = [0]
输出:[0]
1
2
2
# 方法一:迭代法:
思路: 注意题目中给定的要求,是拼接两个链表中的所有节点。要考虑的边界条件是链表节点为空的场景。
我们可以使用迭代的方法实现上述算法,当l1和l2都不是空链表时,判断l1和l2哪一个链表的头节点值更小,将较小的节点添加到结果中去,当一个节点被添加到结果中之后,将对应链表中的节点向后移动一位。
算法: 首先,我们设定一个虚拟节点,这样可以在最后让我们比较容易的返回新合成的链表,我们维护一个cur指针,我们需要做的就是调整它的next指针,然后,我们重复以下过程,直到l1或者l2指向了null。
- 如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 cur 节点的后面同时将 l1 指针往后移一位。
- 否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 cur 向后移一位。
在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function (l1, l2) {
// 创建一个新的链表的头节点
let newHead = new ListNode(-1);
let cur = newHead; // 将head节点赋值给 cur 之后cur 方便移动
// 因为在内部的循环中,会频繁的使用l1.next 和 l2.next 所以为了防止空指针
// 这里需要做防空处理
while (l1 !== null && l2 !== null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 || l2;
return newHead.next;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null {
let dummyHead = new ListNode(-1);
let cur = dummyHead;
while (list1 !== null && list2 !== null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next
}
cur = cur.next
}
// while循环的条件是两个都不为空 退出循环时候肯定有一个为空
cur.next = list1 || list2;
return dummyHead.next
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
链表的题目,对于迭代方法来说,一个虚拟头结点的创建非常必要,这已经算是一种解题技巧了,为了不判断空节点的情况,循环的条件设置为两个非空的形式,在循环条件结束的时候再判断这种情况。
复杂度分析:
- 时间复杂度:O(n + m), 其中 n 和 m 分别为两个链表的长度。因为每次循环迭代中,l1 和 l2 只有一个元素会被放进合并链表中, 因此 while 循环的次数不会超过两个链表的长度之和。所有其他操作的时间复杂度都是常数级别的,因此总的时间复杂度为 O(n+m)
- 空间复杂度:O(1)。我们只需要常数的空间存放若干变量。