将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4输出:1->1->2->3->4->4
想法
我们可以如下递归地定义在两个链表里的 merge 操作(忽略边界情况,比如空链表等):
也就是说,两个链表头部较小的一个与剩下元素的 merge 操作结果合并。
算法
我们直接将以上递归过程建模,首先考虑边界情况。
特殊的,如果 l1
或者 l2
一开始就是 null
,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1
和 l2
哪一个的头元素更小,然后递归地决定下一个添加到结果里的值。如果两个链表都是空的,那么过程终止,所以递归过程最终一定会终止。
var mergeTwoLists = function(l1, l2) {if (l1 === null) {return l2;} else if (l2 === null) {return l1;} else if (l1.val < l2.val) {l1.next = mergeTwoLists(l1.next, l2);return l1;} else {l2.next = mergeTwoLists(l1, l2.next);return l2;}};
复杂度分析
O(n+m)
。因为每次调用递归都会去掉 l1
或者 l2
的头元素(直到至少有一个链表为空),函数 mergeTwoList
中只会遍历每个元素一次。所以,时间复杂度与合并后的链表长度为线性关系O(n+m)
。调用 mergeTwoLists
退出时 l1
和 l2
中每个元素都一定已经被遍历过了,所以 n+m
个帧会消耗 O(n+m)
的空间想法
我们可以用迭代的方法来实现上述算法。我们假设 l1
元素严格比 l2
元素少,我们可以将 l2
中的元素逐一插入 l1
中正确的位置。
算法
prehead
,这可以在最后让我们比较容易地返回合并后的链表。prev
指针,我们需要做的是调整它的 next
指针。l1
或者 l2
指向了 null
l1
当前位置的值小于等于 l2
,我们就把 l1
的值接在 prev
节点的后面同时将 l1
指针往后移一个l2
做同样的操作prev
向后移一个元素。在循环终止的时候, l1
和 l2
至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表。
var mergeTwoLists = function(l1, l2) {// 哨兵结点let prehead = new ListNode(null);let prev = prehead;while (l1 && l2) {if (l1.val <= l2.val) {prev.next = l1;l1 = l1.next;} else {prev.next = l2;l2 = l2.next;}prev = prev.next;}prev.next = l1 == null ? l2 : l1;return prehead.next;};
O(n + m)
,其中 n
和 m
分别为两个链表的长度。因为每次循环迭代中,l1
和 l2
只有一个元素会被放进合并链表中,因此 while
循环的次数不会超过两个链表的长度之和。所有其他操作的时间复杂度都是常数级别的,因此总的时间复杂度为 O(n + m)
O(1)
,我们只需要常数的空间存放若干变量