方法 1:递归
思路
- 首先考虑边界情况。 特殊的,如果 l1 或者 l2 一开始就是 null ,那么没有任何操作需要合并,所以我们只需要返回非空链表。
- 否则,我们要判断 l1 和 l2 哪一个的头元素更小,然后递归地决定下一个添加到链表里的节点。如果两个链表有任意一方为空,那么返回其中非空链表即可,递归终止
- 代码缺点:会破坏原来链表的逻辑位置,因为是原地修改的
- 判断某链表是否为空时,return 另一个链表是代表直接将该链表剩余部分插入合并链表中
NOTACK
判断两个链表的节点是否相同时的return 语句,例子:l1:1->10,l2: 2->9,递归到基例时为return l1(直接结束递归退层),此时再return l1(返回结果链表的指针)
if(l1->val < l2->val){
l1->next = mergeTwoLists(l1->next,l2);
return l1;
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode 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) 的空间。
方法 2:迭代
算法
- 首先,我们设定一个哨兵节点 “prehead” ,让我们比较容易地返回合并后的链表。
- 我们维护一个
prev 指针
,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,不管我们将哪一个元素接在prev.next 后面,我们不仅要把 prev 指针向后移一个元素,两个链表的数据域在比较大小后,相应的也需要将其指针向后移动 - 在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是
有序的
,所以不管哪个链表是非空的,它包含的元素都比前面已经合并链表中的元素都要大。so 我们需将剩下的非空链表接在已经合并链表的后即可
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// maintain an unchanging reference to node ahead of the return node. 维护一个不会改变引用的节点,做返回合并后的链表使用
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// exactly one of l1 and l2 can be non-null at this point, so connect the non-null list to the end of the merged list.
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
复杂度分析
时间复杂度:O(n+m) 。因为每次循环迭代中,l1 和 l2 只有一个元素会被放进合并链表中, while循环的次数等于两个链表的总长度。所有其他工作都是常数级别的,所以总的时间复杂度是线性的。
空间复杂度:O(1) 。迭代的过程只会产生几个指针,所以它所需要的空间是常数级别的。??? NOT ACK 应该还申请了一个链表空间用来存储合并链表才对