45-合并K个升序链表

image-20210528191843266

自己的做法

算法思想

使用队列,将每个链表头指针依次入队。每两个有序链表进行一次合并,然后将他们合并后的链表头指针放入队尾。

循环进行,直到队列中只剩一个元素,就是最终全部合并后的链表头指针。

特殊情况:当链表数组为空,则返回空;链表数组长度为1,则返回该元素。

算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {

public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0) {
return nullptr;
} else if(lists.size() == 1) {
return lists[0];
}
queue<ListNode *> q;
for(int i = 0; i < lists.size(); i++) {
q.push(lists[i]);
}
while (q.size() != 1) {
ListNode * list1 = q.front();
q.pop();
ListNode * list2 = q.front();
q.pop();
ListNode * merged = merge(list1, list2);
q.push(merged);
}
return q.front();
}


ListNode * merge(ListNode * l1, ListNode * l2) {
if(l1 == nullptr) return l2;
if(l2 == nullptr) return l1;

ListNode * dummy = new ListNode();
ListNode * node = dummy, * node1 = l1, * node2 = l2;
while (node1 != nullptr && node2 != nullptr) {
if(node1->val < node2->val) {
node->next = node1;
node1 = node1->next;
} else {
node->next = node2;
node2 = node2->next;
}
node = node->next;
}
if (node1 != nullptr) {
node->next = node1;
}
if(node2 != nullptr) {
node->next = node2;
}
return dummy->next;
}
};

性能分析

设有k个链表,每个链表最长长度为n。

时间复杂度:第一轮合并k2\frac{k}{2}组链表,每一组时间代价是O(2n)O(2n);第二轮合并k4\frac{k}{4},时间代价为O(4n)O(4n)

总时间复杂度为O(kn×logk)O(kn × logk)

空间复杂度:O(k)O(k)

另一种做法

算法思想

每次都从所有链表的当前节点中找出一个最小节点,然后链接该节点。然后该链表遍历指针指向下一个节点。一直到所有链表遍历指针都为空,即所有结点都被连接到有序链表上。循环结束。

类似于合并两个有序链表的做法。

算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {

public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0) {
return nullptr;
} else if(lists.size() == 1) {
return lists[0];
}
ListNode * dummy = new ListNode(0);
ListNode * pre = dummy;
int size = lists.size();
while (pre != nullptr) {
int minNumber = INT_MAX;
int index = -1;
for(int i = 0; i < size; i++) {
if(lists[i] != nullptr && lists[i]->val < minNumber) {
minNumber = lists[i]->val;
index = i;
}
}
//如果初始 链表数组所有指针都为空,那么就不会给index赋值
if(index != -1) {
pre->next = lists[index];
pre = pre->next;
lists[index] = lists[index]->next;
} else {
//也可以直接写成break,结束循环
pre->next = nullptr;
pre = nullptr;
}

}
return dummy->next;
}

};

性能分析

时间复杂度:O(mn)O(mn),m为总的节点个数,n为链表个数。

空间复杂度:O(1)O(1)

参考做法

算法思想

使用优先队列。

优先队列:普通队列是元素在队列尾添加,在队列头删除。在优先队列中,元素被赋予优先级。当从队列弹出时,会首先弹出具有最高优先级的元素。即优先队列具有最高级先出的行为特征。

初始将每个链表头节点依次(判空后)加入优先队列,优先级就是每个节点的值。最高优先级是最小值。

然后从队列中删除最高优先级节点,并添加到已排序链表末尾。判断该节点是否有下一个节点,如果有那么将下一个节点添加到优先队列。

当队列为空,证明所有结点排序完成。

算法实现

C++中,优先队列默认会从大到小排列元素。所以如果要改变默认优先级的话。需要重载operator运算符 <

因为C++优先队列在比较优先级时,会使用该符号比较两个元素大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
struct Status {
int val;
ListNode *ptr;
// 对 < 重载,默认是该元素是否小于rhs元素。返回值改成比较大于、就可以将顺序颠倒
bool operator < (const Status &rhs) const {
return val > rhs.val;
}
};

public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<Status> q;
for(int i = 0; i < lists.size(); i++) {
if (lists[i]) {
q.push({lists[i]->val, lists[i]});
}
}
ListNode * head = new ListNode();
ListNode * pre = head;
while (!q.empty()) {
ListNode * node = q.top().ptr;
q.pop();
pre->next = node;
pre = pre->next;
if(node->next) {
q.push({node->next->val, node->next});
}
}
return head->next;

}

};

性能分析

设有k个链表,每个链表最长长度为n。

时间复杂度:优先队列中元素不超过k个,插入和删除的时间代价为O(logk)O(logk)。最多有kn个点,每个节点都被插入删除依次,所有时间复杂度为O(kn×logk)O(kn × logk)

空间复杂度:O(k)O(k)


45-合并K个升序链表
https://zhaoquaner.github.io/2022/05/11/leetcode/链表/45-合并K个升序链表/
更新于
2022年5月22日
许可协议