轉載本文章請標明作者和出處
本文出自《Darwin的程序空間》
本文題目和部分解題思路來源自《劍指offer》第二版
題目
輸入兩個遞增排序的鏈表,合併這兩個鏈表並使新鏈表中的節點仍然是依照遞增排序的。例如鏈表1和鏈表2合併之後的升序鏈表就是鏈表3;
鏈表定義如下:
public class ListNode {
// 鏈表值
public int val;
// 下一節點的引用
public ListNode next;
// 構造函數
public ListNode(int x) {
val = x;
}
}
解題分析
這是一道非常常見並且簡單的鏈表面試題;
首先是兩個有序鏈表進行合併,切合並完還是要有序的鏈表,我們可以比較兩個有序鏈表的頭節點的大小,然後小的成爲新的鏈表的頭節點,然後再拿這個小的節點的下一個節點和比較大的鏈表的頭節點比較,小的成爲新的頭節點之後的節點;以此類推;直到,一個鏈表的所有節點都已經進入了新的鏈表中,那麼剩下這個鏈表直接嵌入到新的鏈表中即可;
其實,這個不難理解,加入我們有兩隊小學3年級的同學,在操場上按個頭高低站隊,現在兩個班級要合在一起去跑圈,那就是兩個班級各自最矮的一個先出列(也就是站在最前面的兩個人中的一個),然後再在兩個班級中剩下的人依次選出最矮的跟上,一旦一個班級的人都出列了,那麼剩下一個班級剩下的人直接都跟上就好了,因爲原來就是按照大小個去排的;
按照這個思路,我們就可以實現第一種算法,循環法;先定義一個虛擬節點(這樣可以省去判斷頭節點是否爲空的時間,鏈表中經常使用虛擬頭節點來減少判斷),然後進入一個循環,在循環中依次取出兩個鏈表中剩餘節點的最小元素,依次掛在新鏈表的後面即可,一旦一個沒有了節點,剩下一個鏈表直接掛到新鏈表結尾即可;
這種做法不需要多餘的空間存儲變量,只需要遍歷一遍兩個鏈表即可(最壞的情況),時間複雜度是O(m+n),最好的情況就是一個鏈表最小的元素都比另外一個鏈表最大的元素大,這樣時間複雜度就是O(n);
還有一種方式就是通過遞歸,使用虛擬機棧的棧結構,把兩個鏈表中最小的元素一次拼成一個鏈表,最後返回這個鏈表的頭節點,這種做法的時間複雜度和👆的一致,但是空間上,由於遞歸的高度,空間複雜度可能較高;但是寫一遍可以有助於更深刻的理解遞歸,所以建議大家手寫一下,加深對遞歸的印象,我這裏就不多做介紹,代碼會一起貼出來,有不會的同學可以給我留言,我一定解答;
代碼(JAVA實現)
ps:這裏筆者使用的jdk爲1.8版本
- 循環
public class Merge {
public static void main(String[] args) {
ListNode merge = merge(LinkedListUtil.getLinkedList(new int[]{1, 3, 5, 7, 9}), LinkedListUtil.getLinkedList(new int[]{1, 2, 4, 7, 10}));
LinkedListUtil.printLinkedList(merge);
}
/* 循環解法 */
public static ListNode merge(ListNode list1, ListNode list2) {
ListNode vir = new ListNode(-1);
ListNode pointer = vir;
while (true) {
if (Objects.isNull(list1)) {
pointer.next = list2;
break;
}
if (Objects.isNull(list2)) {
pointer.next = list1;
break;
}
if (list1.val <= list2.val) {
pointer.next = list1;
list1 = list1.next;
} else {
pointer.next = list2;
list2 = list2.next;
}
pointer = pointer.next;
}
return vir.next;
}
}
- 遞歸
public class Merge {
public static void main(String[] args) {
ListNode merge = merge(LinkedListUtil.getLinkedList(new int[]{1, 3, 5, 7, 9}), LinkedListUtil.getLinkedList(new int[]{1, 2, 4, 7, 10}));
LinkedListUtil.printLinkedList(merge);
}
/* 遞歸解法 */
public static ListNode merge(ListNode list1, ListNode list2) {
if (Objects.isNull(list1) && Objects.isNull(list2)) {
return null;
} else if (Objects.isNull(list1)) {
return list2;
} else if (Objects.isNull(list2)) {
return list1;
}
if (list1.val <= list2.val) {
list1.next = merge(list1.next, list2);
return list1;
} else {
list2.next = merge(list2.next, list1);
return list2;
}
}
}
LinkedListUtil爲筆者自定義的鏈表工具類,代碼如下:
public class LinkedListUtil {
/**
* 獲取一個鏈表
*/
public static ListNode getLinkedList(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return null;
}
ListNode first = new ListNode(numbers[0]);
ListNode intermediateVariables = first;
for (int i = 1; i < numbers.length; i++) {
ListNode temp = new ListNode(numbers[i]);
intermediateVariables.next = temp;
intermediateVariables = intermediateVariables.next;
}
return first;
}
/**
* 打印一個鏈表
*/
public static void printLinkedList(ListNode first) {
ListNode intermediateVariables = first;
while (!Objects.isNull(intermediateVariables)) {
System.out.printf("%s-> ", intermediateVariables.val);
intermediateVariables = intermediateVariables.next;
}
System.out.println();
}
// 測試工具類
public static void main(String[] args) {
printLinkedList(getLinkedList(new int[]{1, 2, 3, 4, 5, 6, 7}));
}
}