Skip to content

Commit f176072

Browse files
committed
23
1 parent 619655c commit f176072

3 files changed

+275
-13
lines changed

SUMMARY.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
* [19. Remove Nth Node From End of List](leetCode-19-Remov-Nth-Node-From-End-of-List.md)
2323
* [20. Valid Parentheses](leetCode-20-Valid Parentheses.md)
2424
* [21. Merge Two Sorted Lists](leetCode-21-Merge-Two-Sorted-Lists.md)
25-
* [22. Generate Parentheses](leetCode-22-Generate-Parentheses.md)
25+
* [22. Generate Parentheses](leetCode-22-Generate-Parentheses.md)Merge k Sorted Lists
26+
* [23. Merge k Sorted Lists](leetCode-23-Merge-k-Sorted-Lists.md)
2627
* [79. Word Search](leetCode-79-Word-Search.md)

leetCode-21-Merge-Two-Sorted-Lists.md

+3-12
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,11 @@ public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
2424
}
2525
}
2626
if(l1==null){
27-
while(l2!=null){
28-
h.next=l2;
29-
l2=l2.next;
30-
h=h.next;
31-
}
27+
h.next=l2;
3228
}
3329
if(l2==null){
34-
while(l1!=null){
35-
h.next=l1;
36-
l1=l1.next;
37-
h=h.next;
38-
}
39-
}
40-
h.next=null;
30+
h.next=l1;
31+
}
4132
return ans.next;
4233
}
4334
```

leetCode-23-Merge-k-Sorted-Lists.md

+270
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
## 题目描述(困难难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/23.jpg)
4+
5+
k 个有序链表的合并。
6+
7+
我们用 N 表示链表的总长度,考虑最坏情况,k 个链表的长度相等,都为 n 。
8+
9+
# 解法一 暴力破解
10+
11+
简单粗暴,遍历所有的链表,将数字存到一个数组里,然后用快速排序,最后再将排序好的数组存到一个链表里。
12+
13+
```java
14+
public ListNode mergeKLists(ListNode[] lists) {
15+
List<Integer> l = new ArrayList<Integer>();
16+
//存到数组
17+
for (ListNode ln : lists) {
18+
while (ln != null) {
19+
l.add(ln.val);
20+
ln = ln.next;
21+
}
22+
}
23+
//数组排序
24+
Collections.sort(l);
25+
//存到链表
26+
ListNode head = new ListNode(0);
27+
ListNode h = head;
28+
for (int i : l) {
29+
ListNode t = new ListNode(i);
30+
h.next = t;
31+
h = h.next;
32+
}
33+
h.next = null;
34+
return head.next;
35+
}
36+
```
37+
38+
时间复杂度:假设 N 是所有的数字个数,存到数组是 O(N),排序如果是用快速排序就是 $$O(Nlog_N)$$ ,存到链表是 O(N),所以取个最大的,就是 $$O(Nlog_N)$$
39+
40+
空间复杂度:新建了一个链表,O(N)。
41+
42+
# 解法二 一列一列比较
43+
44+
![](https://windliang.oss-cn-beijing.aliyuncs.com/23_2.jpg)
45+
46+
我们可以一列一列的比较,将最小的一个存到一个新的链表里。
47+
48+
```java
49+
public ListNode mergeKLists(ListNode[] lists) {
50+
int min_index = 0;
51+
ListNode head = new ListNode(0);
52+
ListNode h = head;
53+
while (true) {
54+
boolean isBreak = true;//标记是否遍历完所有链表
55+
int min = Integer.MAX_VALUE;
56+
for (int i = 0; i < lists.length; i++) {
57+
if (lists[i] != null) {
58+
//找出最小下标
59+
if (lists[i].val < min) {
60+
min_index = i;
61+
min = lists[i].val;
62+
}
63+
//存在一个链表不为空,标记改完 false
64+
isBreak = false;
65+
}
66+
67+
}
68+
if (isBreak) {
69+
break;
70+
}
71+
//加到新链表中
72+
ListNode a = new ListNode(lists[min_index].val);
73+
h.next = a;
74+
h = h.next;
75+
//链表后移一个元素
76+
lists[min_index] = lists[min_index].next;
77+
}
78+
h.next = null;
79+
return head.next;
80+
}
81+
```
82+
83+
时间复杂度:假设最长的链表长度是 n ,那么 while 循环将循环 n 次。假设链表列表里有 k 个链表,for 循环执行 k 次,所以时间复杂度是 O(kn)。
84+
85+
空间复杂度:N 表示最终链表的长度,则为 O(N)。
86+
87+
其实我们不需要创建一个新链表保存,我们只需要改变得到的最小结点的指向就可以了。
88+
89+
```java
90+
public ListNode mergeKLists(ListNode[] lists) {
91+
int min_index = 0;
92+
ListNode head = new ListNode(0);
93+
ListNode h = head;
94+
while (true) {
95+
boolean isBreak = true;
96+
int min = Integer.MAX_VALUE;
97+
for (int i = 0; i < lists.length; i++) {
98+
if (lists[i] != null) {
99+
if (lists[i].val < min) {
100+
min_index = i;
101+
min = lists[i].val;
102+
}
103+
isBreak = false;
104+
}
105+
106+
}
107+
if (isBreak) {
108+
break;
109+
}
110+
//最小的节点接过来
111+
h.next = lists[min_index];
112+
h = h.next;
113+
lists[min_index] = lists[min_index].next;
114+
}
115+
h.next = null;
116+
return head.next;
117+
}
118+
119+
```
120+
121+
时间复杂度:假设最长的链表长度是 n ,那么 while 循环将循环 n 次。假设链表列表里有 k 个链表,for 循环执行 k 次,所以时间复杂度是 O(kn)。
122+
123+
空间复杂度:O(1)。
124+
125+
# 解法三 优先队列
126+
127+
解法二中,我们每次都是取出一个最小的,然后加入一个新的, O(1)的复杂度,再找最小的,O(k) 的复杂度。我们完全可以用一个优先队列。
128+
129+
![](https://windliang.oss-cn-beijing.aliyuncs.com/23_3.jpg)
130+
131+
我们将优先级定义为数越小优先级越高,如果用堆实现优先队列,这样我们每次找最小不再需要 O(k),而是 O(log(k)),当然这样的话,我们加入新的话不再是 O(1),也需要 O(log(k))。可以看看[这里](http://blog.51cto.com/ahalei/1425314?source=dra)[这里](http://blog.51cto.com/ahalei/1427156)
132+
133+
``` java
134+
public ListNode mergeKLists(ListNode[] lists) {
135+
//定义优先队列的比较器
136+
Comparator<ListNode> cmp;
137+
cmp = new Comparator<ListNode>() {
138+
@Override
139+
public int compare(ListNode o1, ListNode o2) {
140+
// TODO Auto-generated method stub
141+
return o1.val-o2.val;
142+
}
143+
};
144+
145+
//建立队列
146+
Queue<ListNode> q = new PriorityQueue<ListNode>(cmp);
147+
for(ListNode l : lists){
148+
if(l!=null){
149+
q.add(l);
150+
}
151+
}
152+
ListNode head = new ListNode(0);
153+
ListNode point = head;
154+
while(!q.isEmpty()){
155+
//出队列
156+
point.next = q.poll();
157+
point = point.next;
158+
//判断当前链表是否为空,不为空就将新元素入队
159+
ListNode next = point.next;
160+
if(next!=null){
161+
q.add(next);
162+
}
163+
}
164+
return head.next;
165+
}
166+
```
167+
168+
时间复杂度:while 循环依旧取决于最长的链表长度 n,while 循环里边,如果有 k 个链表,入队出队都需要 log(k),除此之外还有初始化队列的时间复杂度 O(k)。所以时间复杂度是 O(min(k,nlog(k)))。
169+
170+
空间复杂度:优先队列需要 O(k)的复杂度。
171+
172+
# 解法四 两两合并
173+
174+
利用[之前](https://leetcode.windliang.cc/leetCode-21-Merge-Two-Sorted-Lists.html)合并两个链表的算法,我们直接两两合并,第 0 个和第 1 个链表合并,新生成的再和第 2 个链表合并,新生成的再和第 3 个链表合并...直到全部合并完。
175+
176+
```java
177+
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
178+
ListNode h = new ListNode(0);
179+
ListNode ans=h;
180+
while (l1 != null && l2 != null) {
181+
if (l1.val < l2.val) {
182+
h.next = l1;
183+
h = h.next;
184+
l1 = l1.next;
185+
} else {
186+
h.next = l2;
187+
h = h.next;
188+
l2 = l2.next;
189+
}
190+
}
191+
if(l1==null){
192+
h.next=l2;
193+
}
194+
if(l2==null){
195+
h.next=l1;
196+
}
197+
return ans.next;
198+
}
199+
public ListNode mergeKLists(ListNode[] lists) {
200+
if(lists.length==1){
201+
return lists[0];
202+
}
203+
if(lists.length==0){
204+
return null;
205+
}
206+
ListNode head = mergeTwoLists(lists[0],lists[1]);
207+
for (int i = 2; i < lists.length; i++) {
208+
head = mergeTwoLists(head,lists[i]);
209+
}
210+
return head;
211+
}
212+
```
213+
214+
时间复杂度:不妨假设是 k 个链表并且长度相同,链表总长度为 N,那么第一次合并就是 N/k 和 N/k ,第二次合并就是 2 \* N/k 和 N/k,第三次合并就是 3 \* N/k 和 N / k,总共进行 n - 1 次合并,每次合并的时间复杂度是 O(n),所以总时间复杂度就是$$O(\sum_{i=1}^{k-1}(i*\frac{N}{k}+\frac{N}{k}))=O(kN)$$,可以将两项分开,N/k 其实是常数,分开的第一项是等差数列。
215+
216+
空间复杂度:O(1)。
217+
218+
#解法五 两两合并优化
219+
220+
依旧假设是 k 个链表,合并的过程优化下,使得只需要合并 log(k)次。
221+
222+
![](https://windliang.oss-cn-beijing.aliyuncs.com/23_4.jpg)
223+
224+
```java
225+
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
226+
ListNode h = new ListNode(0);
227+
ListNode ans=h;
228+
while (l1 != null && l2 != null) {
229+
if (l1.val < l2.val) {
230+
h.next = l1;
231+
h = h.next;
232+
l1 = l1.next;
233+
} else {
234+
h.next = l2;
235+
h = h.next;
236+
l2 = l2.next;
237+
}
238+
}
239+
if(l1==null){
240+
h.next=l2;
241+
}
242+
if(l2==null){
243+
h.next=l1;
244+
}
245+
return ans.next;
246+
}
247+
public ListNode mergeKLists(ListNode[] lists) {
248+
if(lists.length==0){
249+
return null;
250+
}
251+
int interval = 1;
252+
while(interval<lists.length){
253+
System.out.println(lists.length);
254+
for (int i = 0; i + interval< lists.length; i=i+interval*2) {
255+
lists[i]=mergeTwoLists(lists[i],lists[i+interval]);
256+
}
257+
interval*=2;
258+
}
259+
260+
return lists[0];
261+
}
262+
```
263+
264+
时间复杂度:假设每个链表的长度都是 n ,那么时间复杂度就是$$O(\sum_{i=1}^{log_2k}n)=O(nlogk)$$
265+
266+
空间复杂度:O(1)。
267+
268+
#
269+
270+
优先队列的运用印象深刻,此外对两两链表的合并,我们仅仅改变了合并的方式就将时间复杂度降低了很多,美妙!

0 commit comments

Comments
 (0)