1. 题目描述
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
2. 解题方法及代码
2.1 哨兵节点法
哨兵节点是一个不保存任何有效数据的节点(该节点的值往往设置为-1或者null),只是作为标志一个链表的开头。可以用一个哨兵节点作为一个新链表的开头,然后通过比较原有两个链表中节点的大小依次将待合并的节点连接到该链表上,最后返回新链表的第二个节点(即除去哨兵节点的链表头)即可。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode p0 = new ListNode(-1); // 新建一个哨兵节点,p0指向这个哨兵节点
ListNode newl = p0; // 表示新链表尾部的指针
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
newl.next = l1;
l1 = l1.next;
newl = newl.next;
} else {
newl.next = l2;
l2 = l2.next;
newl = newl.next;
} //比较两个链表中的较小值,将较小值添加到新链表中,然后原链表指针和新链表指针都向后移动一位
}
if (l1 == null) newl.next = l2;
else newl.next = l1;
return p0.next; // 返回新链表中除去哨兵节点的第一个节点
}
}
时间复杂度O(m+n),空间复杂度O(1)
LeetCode提交结果:1 ms 39.4 MB
2.2 递归法
以l1: 1->3->5和l2: 2->4->6为例
我们可以认为,这个函数的返回值是一个已经排序合并好的链表的链表头,在每一步我们都把目前待比较的两个节点中较小的那个节点,连接到除它以外的所有未排序节点排序连接以后的链表上去。
第一步:l1=1,l2=2,l1<l2,因此我们需要把l1连接到除它以外的,也就是3->5和2->4->6两个链表合并以后的那个链表上去。2->4->6就是l2链表,而3->5是l1.next链表。因此有l1.next = mergeTwoLists(l1.next, l2);它的返回值为带上l1=1以后的排序好的链表的表头,也就是返回l1(return l1;)。
第二步:mergeTwoLists(l1.next, l2);,也就是此时l1=l1.next, l2=l2。l1=3, l2=2, l1>l2,因此我们需要把l2连接到除它以外的,也就是3->5和4->6两个链表合并以后的那个链表上去。4->6就是l2.next链表,而3->5是目前的l1链表(因为已经l1 = l1.next了)。因此有l2.next = mergeTwoLists(l1, l2.next); 它的返回值为带上l2=2以后排序好的链表的表头,也就是返回l2(return l2;).
以此类推… 直到第4步结束,此时l1 = 5,l2=6。
第5步:mergeTwoLists(l1.next, l2);,此时l1=l1.next=null, l2=6。由于l1已经为空链表,返回剩余的l2=6(由于链表有序,可以确定剩余的一定比所有已排序的节点都大)。也就是说l2=6接在了旧的l1,也就是5的后面。这样所有的节点都依次连接了。
解题的关键是把递归调用的返回值看作除了目前最小的节点外,其余节点都已经排序好了的链表,只要把目前的最小节点接在这个链表前面就可以了。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1; // 既用来解决有一个空链表的情况,也是迭代的返回点
if (l1.val <= l2.val) {
l1.next = mergeTwoLists(l1.next, l2); // 当l1的当前节点较小时,它应该被连接至后面已经排好的链表上
return l1; // 返回这个l1点及它后面排好的点
}
else {
l2.next = mergeTwoLists(l1, l2.next); // l2同理
return l2;
}
}
}
时间复杂度:O(m+n),空间复杂度O(m+n)。
LeetCode提交结果:1 ms 39.4 MB