合并k个排序链表: 合并给定k个排序链表;
e.g.
2->4->nullptr
-4->4->5->nullptr
-1->nullptr
合并这3个有序的链表, 返回: -4->-1->2->4->4->5->nullptr
解法思路
主要思路是建堆MinHeap。从k个链表表头取元素, 建堆, 然后heap.peek(), heap.remove; 持续的取每个链表,直到取完全部元素; 在heap.peek(), heap.remove();
注意, 对std::vector<ListNode*>记录的list的指针, 每次取完就更新为next, 直到next为nullptr, 说明取完了当前的链表. 这时k计数应该递减(注意不应该重复递减);
mergeKLists函数返回值是ListNode*, 注意head,tail指针的更新 - 需要符合题意,返回链表, 不是vector等什么(踩坑);
#include <iostream>
#include <vector>
#include "../heap/heap.h"
typedef struct listNode {
int value;
struct listNode* next;
listNode() = default;
listNode(int val, struct listNode* p):
value(val), next(p) {}
listNode(const listNode& node) {
value = node.value;
next = node.next;
}
bool operator>(const listNode& l) {
return value > l.value;
}
bool operator<(const listNode& l) {
return !(operator>(l));
}
bool operator==(const listNode& l) {
return value == l.value;
}
} ListNode;
ListNode* mergeKLists(std::vector<ListNode*>& lists, MinHeap<ListNode>& heap) {
int k = lists.size() - 1;
bool marked[k+1];
ListNode* head = nullptr, *tail = nullptr;
while (k >= 0) {
for(unsigned int j = 0; j < lists.size(); ++j) {
if (lists[j] != nullptr) {
heap.insert(*(lists[j]));
lists[j] = lists[j]->next;
} else {
if (!marked[j]) {
k--;
marked[j] = true;
}
}
}
ListNode* node = new ListNode(heap.peek());
if (head == nullptr) {
head = node;
heap.remove();
} else {
tail->next = node;
heap.remove();
}
tail = node;
}
while(heap.size() > 0) {
ListNode* node = new ListNode(heap.peek());
heap.remove();
tail->next = node;
tail = node;
}
return head;
}
int main()
{
// 2->4->nullptr
// -4->4->5->nullptr
// -1->nullptr
ListNode n1 = {4, nullptr};
ListNode n2 = {2, &n1};
ListNode m1 = {5, nullptr};
ListNode m2 = {4, &m1};
ListNode m3 = {-4, &m2};
ListNode l1 = {-1, nullptr};
std::vector<ListNode*> kk = {&n2, &m3, &l1};
MinHeap<ListNode> heap;
ListNode* head = mergeKLists(kk, heap);
for (ListNode* p = head; p != nullptr; p = p->next) {
if (p == head)
std::cout << p->value;
else
std::cout << ", " << p->value;
}
}
上述代码, 利用了上一篇"堆的基本实现"的代码 - MinHeap; 针对这道题解法并不是很好. 比较了七月君的林老师的答案, 总结起来有以下几点:
- insert建堆 vs floyd建堆; insert建堆性能差; 林老师的建堆的过程是可以优化成floyd建堆的, 还是利用shiftDown, 只是计数范围的改变(0 - floor(n/2) - 1);
- 林老师的解法是针对链表头建堆, 堆一共有k个元素, 摘取后替换为linklist中下一个元素,调整堆属性; 当某一个节点出现nullptr, 说明该链表结束, 补充堆尾再调整; 这个方法思路较我针对每一个节点建堆对的规模较小, 在堆中shiftDown调整次数较少;
- 林老师的解法,单独抽取了建堆和shiftDown的逻辑,整体上较为简洁; 我依赖MinHeap的写法虽然也行, 但需要注意, 此类问题可以不用完整写出堆或别的某种数据结构的完整逻辑, 而只借助其部分逻辑的实现完成, 形式更简洁;
- MinHeap的实现需要struct这类用户定义类型去重载operator<, operator>, opeartor==, 并且提供default constructor;
下面列一下林老师的解法(建堆的地方改成floyd build heap)。
#include <iostream>
#include <vector>
typedef struct listNode {
int value;
struct listNode* next;
} ListNode;
int shiftDown(std::vector<ListNode*>& heap, int i);
ListNode* mergeKList(std::vector<ListNode*>& lists) {
std::vector<ListNode*> heap;
for (auto beg = lists.begin(); beg != lists.end(); ++beg) {
heap.push_back(*beg);
}
// floyd build heap;
int n = heap.size();
for (int j = n/2 - 1; j >= 0; --j) {
shiftDown(heap, j);
}
for (unsigned int j = 0; j < heap.size(); ++j) {
std::cout << heap[j]->value;
}
std::cout << std::endl;
ListNode* head = nullptr, *tail = nullptr;
while (heap.size() > 0) {
ListNode* node = heap[0];
heap[0] = heap[0]->next;
// 当用完一个链, 把堆尾元素补到堆首, 删除堆尾, 调整堆;
if (head == nullptr) {
heap[0] = heap[heap.size() - 1];
heap.pop_back();
}
shiftDown(heap, 0);
}
return head;
}
void swap(std::vector<ListNode*>& heap, int i, int j) {
ListNode* t = heap[i];
heap[i] = heap[j];
heap[j] = t;
}
int shiftDown(std::vector<ListNode*>& heap, int i) {
int n = heap.size();
int k = 2 * i + 1;
int h = 2 * i + 2;
while (k < n || h < n) {
int left_val, right_val, idx;
if (k < n) {
left_val = heap[k]->value;
}
if (h < n) {
right_val = heap[h]->value;
}
if (k < n && h < n) {
idx = left_val < right_val ? k : h;
} else {
idx = k;
}
if (heap[i]->value > heap[idx]->value) {
swap(heap, i, idx);
i = idx;
k = 2 * i + 1;
h = 2 * i + 2;
} else
break;
}
return i;
}
int main() {
ListNode n1 = {4, nullptr};
ListNode n2 = {2, &n1};
ListNode m1 = {5, nullptr};
ListNode m2 = {4, &m1};
ListNode m3 = {-4, &m2};
ListNode l1 = {-1, nullptr};
std::vector<ListNode*> kk = {&n2, &m3, &l1};
ListNode* head = mergeKList(kk);
for (ListNode* p = head; p != nullptr; p = p->next) {
if (p == head)
std::cout << p->value;
else
std::cout << ", " << p->value;
}
}
总结
- 如何建堆,如何调整更加巧妙,是个问题.
- 堆的重点基本操作的实现, 有时候单拎出来实现也是需要的; 重点数据结构的重点方法,有时也需要理解记忆。