原题链接
说实话我看到这题吓到了,然后多少心态有点崩,连代码都没写就直接投降了。我确实有一个思路——不过这个思路也是我大学时听了《数据结构与算法》课听到的,大致简述如下:
两张表同时遍历,然后将比较两个表的头节点,小的节点并入已排序的表中,大的节点继续保留;然后下一次遍历继续比较两个表的头节点。
我之后自己又尝试用迭代去写,用了unsortedHead和sortedEnd两个指针来指向未排序的表头和排序的表尾,很明显我的思路是错的。
使用迭代的正确方法是:用一个prehead指向已排序表的表头,用于作为函数返回值;用一个prev指向已排序表的表尾。l1和l2两个引用变量是动态指向不断缩短的l1和l2表头的。实际上我认为其实用一个引用变量作为标记——或者说维护已排序表的表尾就好了,但是一直没想到具体怎么做。我认为可能是没有意识到可以用l1=l1->next这样覆盖原引用变量的方法来更新指针吧。
而官方解答也确实是这样的思路,采用了归并和迭代两种处理方法。
官方解答
迭代
迭代的思路在上面已经说明了,补充一下循环迭代结束后的思路。在循环结束后,l1和l2至多有一个是非空的,而非空链表的剩余最后一个元素一定比已排序链表中的所有元素都大,因此我们只需要直接将其接到已排序链表的尾部即可。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode preHead = new ListNode(-1);
ListNode prev = preHead;//注意preHead和prev不是指针,是引用
while(l1 != null && l2 != null){//prev指向了l1或l2表的尽头
if(l1.val<=l2.val){
prev.next=l1;
l1=l1.next;
}else{
prev.next=l2;
l2=l2.next;
}
prev=prev.next;//prev也要前移
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
// 要么是 l1 为 null 要么是 l2 为 null
prev.next = l1 == null ? l2 : l1;
return preHead.next;
}
}
时间复杂度是 O ( n + m ) O(n+m) O(n+m),因为我们需要处理n+m个元素;空间复杂度是 O ( 1 ) O(1) O(1)。因为无论l1和l2多长我们只需要preHead和prev两个变量的存储空间。
归并
可以递归地定义两个链表里的merge操作。
当
l
1
[
0
]
<
l
2
[
0
]
l1[0]<l2[0]
l1[0]<l2[0]时:
l
1
[
0
]
+
m
e
r
g
e
(
l
1
[
1
:
]
,
l
2
)
l1[0]+merge(l1[1:],l2)
l1[0]+merge(l1[1:],l2)
否则:
l
2
[
0
]
+
m
e
r
g
e
(
l
1
,
l
2
[
1
:
]
)
l2[0]+merge(l1,l2[1:])
l2[0]+merge(l1,l2[1:])
我们需要的结果就是两个链表头部较小的一个节点与剩下元素的merge操作结果合并。
我们要考虑边界情况(也是递归调用的终止情况):如果l1或l2其中有一个是空链表,那么我们只需要返回两者中非空的一者。否则我们要判断l1和l2中哪一个链表的头节点的值更小,更小的那一个头节点就作为剩余元素归并后的表的头节点。
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 ( m + n ) O(m+n) O(m+n),因为每次调用都会去掉l1表或者l2表的头节点;空间复杂度为 O ( m + n ) O(m+n) O(m+n),递归调用mergeTwoLists函数需要消耗栈空间,栈空间的大小取决于递归调用的深度。因此递归调用时mergeTwoList函数最多调用n+m次。