原題
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
Example:
Input:
[
1->4->5,
1->3->4,
2->6
]
Output: 1->1->2->3->4->4->5->6
解析
大體上有兩種思路:
思路一:divide and conquer
根據divide-and-conquer思想,將k路合併的歸併排序問題,化歸爲2路合併兩個子串(分別再對其k路合併)的問題,迭代解決。主要思想:
k路和並(n個list)=2路合併(k路合併(n/2個list), k路合併(n/2個list))
有些細節問題:1個列就直接輸出了
假設list平均長度爲n,總共有k個數列,則:
時間複雜度 | O(knlog(k)) |
空間複雜度 | O(1) |
解釋:
時間複雜度:8個子串要分成4, 2, 1個字串才能解決問題,再重新向上merge。所以,對於每個列表中的元素來說,我最多要對其做次merge的過程,並且總共kn個元素。將兩項相乘,算法複雜度由此可得。
空間複雜度:若不要求不能修改輸入的鏈表,實際上可以在原來的鏈表上直接操作(或複製一份,這個複雜度就不計入算法啦)。2路歸併排序中,將指針重新安排就行,不需要額外空間。這裏,不考慮遞歸過程中一些瑣碎的變量所佔的空間。
思路二: priority queue
直接硬槓k路歸併,維護一個大小爲k的優先隊列。開始時,存入所有隊列第一個元素。然後不斷取出Listnode(優先隊列保證是當前隊列中最小的元素),加入輸出的列表。並且:
- 若該Listnode不爲原數列中最後一個元素,將原數列的下一個Listnode加入優先隊列中
- 若未最後一個,啥都不做
不斷循環直到優先隊列爲空。這個過程很好理解,總結爲一句話:不斷拿出所有隊列頭上元素中最小的。因爲我隊列是遞增的,那麼我一個隊列頭上的元素保證是隊列中最小的,那頭上元素最小的則爲剩下元素中最小的。
假設list平均長度爲n,總共有k個數列,則:
時間複雜度 | O(knlog(k)) |
空間複雜度 | O(1) |
還是一樣,優先隊列取一個元素和放一個元素複雜度都爲O(log(k)),總共有kn個元素。所以算法複雜度=O(kn*log(k)* 2)=O(knlog(k))。空間複雜度和思路一同理。
思考
這道題難關主要在於手動實現一個Priority Queue。其實最小堆的思想很簡單,上手很快,不復雜。這道題被分爲hard可能就是因爲這個原因吧(然而我覺得並不hard)。
運行速度
我只實現了思路一:
答案
1.思路一的代碼:discussion
Note: discussion裏的思路一的代碼好像不太高效,用了vector再去存新的List。然而完全沒必要,有興趣的讀者可以自己實現一個更加高效的版本。
2.思路二的代碼(博主實現的):
class PriorityQueue {
public:
vector<ListNode*> nodes;
int size;
PriorityQueue(int listNum);
void push(ListNode* objNode);
ListNode* pop();
int isEmpty();
};
PriorityQueue::PriorityQueue(int listNum) : nodes(vector<ListNode*>(listNum + 1)), size(0) {}
void PriorityQueue::push(ListNode* objNode) {
int nowNode = this->size + 1;
this->size += 1;
nodes[this->size] = objNode;
while ((nowNode / 2 != 0) && (this->nodes[nowNode / 2]->val
> this->nodes[nowNode]->val)) {
ListNode* tmp;
tmp = this->nodes[nowNode];
this->nodes[nowNode] = this->nodes[nowNode / 2];
this->nodes[nowNode / 2] = tmp;
nowNode = nowNode / 2;
}
}
ListNode* PriorityQueue::pop() {
ListNode* retNode = this->nodes[1];
int nowIndex = 1;
this->nodes[1] = this->nodes[this->size];
this->size -= 1;
for (;;) {
int leftIndex = nowIndex * 2;
int rightIndex = nowIndex * 2 + 1;
int toIndex = 0;
int toSwap = 0;
int nowVal = this->nodes[nowIndex]->val;
if ((leftIndex <= this->size) && (rightIndex <= this->size)) {
int leftVal = this->nodes[leftIndex]->val;
int rightVal = this->nodes[rightIndex]->val;
if (leftVal < rightVal) {
if (leftVal < nowVal) {
toSwap = 1;
toIndex = leftIndex;
}
} else {
if (rightVal < nowVal) {
toSwap = 1;
toIndex = rightIndex;
}
}
} else if ((leftIndex <= this->size) && (this->nodes[leftIndex]->val <
this->nodes[nowIndex]->val)) {
toSwap = 1;
toIndex = leftIndex;
} else if ((rightIndex <= this->size) && (this->nodes[rightIndex]->val <
this->nodes[nowIndex]->val)) {
toSwap = 1;
toIndex = rightIndex;
}
if (toSwap == 1) {
ListNode* tmp;
tmp = this->nodes[nowIndex];
this->nodes[nowIndex] = this->nodes[toIndex];
this->nodes[toIndex] = tmp;
nowIndex = toIndex;
} else break;
}
return retNode;
}
int PriorityQueue::isEmpty() {
if (this->size == 0) {
return 0;
} else {
return 1;
}
}
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode* retList = NULL;
ListNode* nowListPointer = NULL;
PriorityQueue* pQueue = new PriorityQueue(lists.size());
if (lists.size() == 0) return NULL;
for (int i = 0; i < lists.size(); ++i) {
if (lists[i] != NULL) pQueue->push(lists[i]);
}
while (pQueue->isEmpty() == 1) {
ListNode* currentNode = pQueue->pop();
if (retList == NULL) {
retList = currentNode;
nowListPointer = currentNode;
} else {
nowListPointer->next = currentNode;
nowListPointer = nowListPointer->next;
}
if (currentNode->next) pQueue->push(currentNode->next);
}
return retList;
}
};