题目描述:
输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。例如输入图中的链表1和链表2,则合并之后的升序链表3所示。
分析:
这是一个经常被各公司采用的面试题。在面试过程中,我们发现应聘者最容易犯两个错误:一是在写代码之前没有对合并的过程想清楚,最终合并出来的链表要么中间断开要么并没有递增排序;二是代码的鲁棒性方面存在问题,程序一旦有特殊的输入(如空链表)就会崩溃。接下来分析如何解决这个问题。
首先分析合并两个链表的过程。我们的分析从合并两个链表的头结点开始。链表1的头结点的值小于链表2的头结点的值,因此链表1的头结点将是合并后链表的头结点,如图所示:
我们继续合并两个链表中剩余的结点。在两个链表中剩下的结点依然是排序的,因此合并这两个链表的步骤和前面的步骤是一样的。我们还是比较两个头结点的值。此时链表2的头结点的值小于链表1的头结点的值,因此链表2的头结点的值将是合并剩余结点得到的链表的头结点。我们把这个结点和前面合并链表时得到的链表的尾节点链接起来,如图所示。
当我们得到两个链表中值较小的头结点并把它链接到已经合并的链表之后,两个链表剩余的结点依然是排序的,因此合并的步骤和之前的步骤是一样的。这就是典型的递归的过程,我们可疑定义递归函数完成这一合并过程。
接下来我们来解决鲁棒性的问题。每当代码试图访问空指针指向的内存时程序就会崩溃,从而导致鲁棒性问题。在本题中一旦输入空的链表就会引入空的指针,因此我们要对空链表单独处理。当第一个链表是空链表,也就是它的头结点是一个空指针时,那么把它和第二个链表合并,闲人合并的过程就是第二个链表。同样,当输入的第二个链表的头结点是空指针的时候,我们把它和第一个链表合并得到的结果就是第一个链表。如果两个链表都是空链表,合并的结果是得到一个空链表。
/**
* 合并两个排序的链表
*/
public class MergeList {
// 使用递归的方式实现
public ListNode merge(ListNode list1, ListNode list2) {
/**
* 如果输入的链表中只要有一个为null,就返回另一个
*/
if (list1 == null) {
return list2;
} else if (list2 == null) {
return list1;
}
// 合并后链表的头节点
ListNode mergeHead = null;
if (list1.val < list2.val) {
mergeHead = list1;
mergeHead.next = merge(list1.next, list2);
} else {
mergeHead = list2;
mergeHead.next = merge(list1, list2.next);
}
return mergeHead;
}
// 使用循环实现
public ListNode merge2(ListNode list1, ListNode list2) {
if (list1 == null) {
return list2;
} else if (list2 == null) {
return list1;
}
ListNode mergeHead = null;
if (list1.val < list2.val) {
mergeHead = list1;
list1 = list1.next;
}
else {
mergeHead = list2;
list2 = list2.next;
}
ListNode tmpNode = mergeHead;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
tmpNode.next = list1;
list1 = list1.next;
} else {
tmpNode.next = list2;
list2 = list2.next;
}
tmpNode = tmpNode.next;
}
if (list1 == null) {
tmpNode.next = list2;
} else {
tmpNode.next = list1;
}
return mergeHead;
}
}
class ListNode {
int val;
ListNode next;
}