合併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;
}
}
總結
- 如何建堆,如何調整更加巧妙,是個問題.
- 堆的重點基本操作的實現, 有時候單拎出來實現也是需要的; 重點數據結構的重點方法,有時也需要理解記憶。