题目来源
题目描述
题目解析
这道题是leetcode:21. 合并两个有序链表的扩展,由合并两个变成了合并k个了。但是不管是合并几个,基本还是要两两合并的。
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
}
};
逐一合并两条链表
因此,我们可以先前两个合并,合并好了之后再跟第三个、然后第四个直到第k个,代码如下:
class Solution {
ListNode *mergeTwoLists(ListNode *l1, ListNode *l2){
ListNode *dummy = new ListNode(-1);
ListNode *move = dummy;
while (l1 && l2){
if(l1->val < l2->val){
move->next = l1;
l1 = l1->next;
}else{
move->next = l2;
l2 = l2->next;
}
move = move->next;
}
move->next = l1 ? l1 : l2;
return dummy->next;
}
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode *ans = nullptr;
for (int i = 0; i < lists.size(); ++i) {
ans = mergeTwoLists(ans, lists[i]);
}
return ans;
}
};
分治合并
简单来说就是不停的对半划分,比如k个链表先划分为合并两个 k/2 个链表的任务,再不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。
举个例子来说比如合并6个链表,那么按照分治法,首先分别合并0和3,1和4,2和5。这样下一次只需合并3个链表,再合并1和3,最后和2合并就可以了
代码中的k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始终从后半段开始,比如当 n=5 时,那么此时 k=3,则0和3合并,1和4合并,最中间的2空出来。当n是偶数的时候,加1也不会有影响,比如当 n=4 时,此时 k=2,那么0和2合并,1和3合并,完美解决问题
递归写法
class Solution {
ListNode * mergeTwoLists(ListNode*l1, ListNode*l2){
if(l1 == nullptr){
return l2;
}
if(l2 == nullptr){
return l1;
}
if(l1->val < l2->val){
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}else{
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
ListNode *merge(vector<ListNode*>& lists, int lo, int hi){
if(lo == hi){
return lists[lo];
}
int mid = lo + (hi - lo) / 2;
ListNode *l1 = merge(lists, lo, mid);
ListNode *l2 = merge(lists, mid + 1, hi);
return mergeTwoLists(l1, l2);
}
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()){
return NULL;
}
return merge(lists, 0, lists.size() - 1);
}
};
迭代写法
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.empty()) return NULL;
int n = lists.size();
while (n > 1) {
int k = (n + 1) / 2;
for (int i = 0; i < n / 2; ++i) {
lists[i] = mergeTwoLists(lists[i], lists[i + k]);
}
n = k;
}
return lists[0];
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(-1), *cur = dummy;
while (l1 && l2) {
if (l1->val < l2->val) {
cur->next = l1;
l1 = l1->next;
} else {
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
if (l1) cur->next = l1;
if (l2) cur->next = l2;
return dummy->next;
}
};
k指针
K 个指针分别指向 K 条链表
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int k = lists.size();
ListNode *dummy = new ListNode(-1);
ListNode *move = dummy;
while (true){
ListNode *minNode = NULL;
int minPointer = -1;
for (int i = 0; i < k; ++i) {
if(lists[i] == NULL){
continue;
}
if(minNode == NULL || lists[i]->val < minNode->val){
minNode = lists[i];
minPointer = i;
}
}
if (minPointer == -1) {
break;
}
move->next = minNode;
move = move->next;
lists[minPointer] = lists[minPointer]->next;
}
return dummy->next;
}
};
优先队列
可以使用优先队列对k指针进行优化。
- 将链表的值存入小根堆中,再逐次将堆顶取出连接成链表
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
auto cmp = [](ListNode *&a, ListNode *& b){
return a->val > b->val;
};
std::priority_queue<ListNode *, std::vector<ListNode*>, decltype(cmp)> q(cmp);
for(auto l : lists){
if(l){
q.push(l);
}
}
ListNode *dummy = new ListNode(-1), *move = dummy;
while (!q.empty()){
auto t = q.top(); q.pop();
move->next = t;
move = move->next;
if(move->next){
q.push(move->next);
}
}
return dummy->next;
}
};
基数排序
思路:
- 将所有的节点值出现的最大值和最小值都记录下来,然后记录每个节点值出现的次数
- 这样从最小值遍历到最大值的时候,就会按顺序经过所有的结点值,根据其出现的次数,建立相对应个数的结点。
但是这种解法有个特别需要注意的地方,那就是合并后的链表结点都是重新建立的,若在某些情况下,不能新建结点,而只能交换或者重新链接结点的话,那么此解法就不能使用
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
std::unordered_map<int, int> m;
int max = INT_MIN, min = INT_MAX;
for(auto node : lists){
ListNode *t = node;
while (t){
max = std::max(max, t->val);
min = std::min(min, t->val);
++m[t->val];
t = t->next;
}
}
ListNode *dummy = new ListNode(-1), *move = dummy;
for (int i = min; i <= max; ++i) {
if(m.count(i) == false){
continue;
}
for (int j = 0; j < m[i]; ++j) {
move->next = new ListNode(i);
move = move->next;
}
}
return dummy->next;
}
};
类似题目
题目 | 核心思路 |
---|---|
leetcode:21. 合并两个有序链表 Merge Two Sorted Lists | |
leetcode:23. 合并K个升序链表Merge K Sorted Lists | |
leetcode:264. 返回第n个丑数 I Ugly Number III |