LeetCode題目 合併K個排序鍊錶

2021-09-25 19:02:02 字數 3600 閱讀 8308

合併k個排序鍊錶是我在刷leetcode的時候遇到的題目,描述大致如下:

合併 k 個排序鍊錶,返回合併後的排序鍊錶。請分析和描述演算法的複雜度。

輸入:

[ 1->4->5,

1->3->4,

2->6

]輸出: 1->1->2->3->4->4->5->6

一開始的思路是類似於合併兩個有序鍊錶的思路,就是每次在這k個鍊錶中取出乙個最小的節點,以此類推,不過這裡還存在乙個問題,那就是對於兩個鍊錶來說,只需要乙個簡單的判斷就可以得到它們之中的最值了,而對於k個鍊錶則不行,所以,我們還需要乙個類似於緩衝區一樣的東西。步驟大致如下:

從這k個鍊錶中遍歷取出第乙個不為nullptr的節點並放入緩衝區中

從緩衝區中拿出乙個最小值,取名為min

判斷min->next是否指向了nullptr,如果不為空的話,則以min->next節點代替之前的min節點。

重複2-3步驟直至緩衝區為空即可。

根據描述可以推斷出此法可行,不過需要對緩衝區進行一定的優化,因為每次取的都是最值,所以我們可以對這個緩衝區進行排序。

我第乙個想到的就是插入排序,原因無它,簡單。

/*倒序*/

void insertone(vector& list, listnode* node)

//插入

list.insert(it, node);

}

insertone就是不完整插入排序,該函式需要保證list在這之前已經完全有序。

這裡採用的是倒序,即最小值在最後的位置,這樣的好處就是在取出來的時候不需要把所有的元素都向前移位。

listnode* mergeklists(vector& lists) 

return result->next;

}

mergeklist就是合併這k個鍊錶的函式。

result指標儲存著插入的結果,它用到了dummy head(頭節點),使用它可以避免對特殊情況進行判斷。

queue陣列則是之前談到的緩衝區。

//從佇列中取出最小值

auto min = queue.back();

p->next = min;

p = p->next;

queue.pop_back();

從緩衝區queue中取出最小值,然後把這個值鏈到了result頭節點鍊錶之中。

//判斷是否存在後續

if (min->next != nullptr)

insertone(queue, min->next);

由於在一開始存放的是各個非空鍊錶的頭指標,所以可以憑藉著它的next的值可以遍歷這個鍊錶。

假設k=3.資料為, , }。

按照之前的思路,緩衝區如下:

此時我們取出的最小值min則是鍊錶中的值為1的節點,接著我們判斷該節點的next不為空,則去除掉min節點。

接著把插入到緩衝區中。

這時彈出的則是緩衝區最右邊的值為1的節點,以此類推,直至緩衝區為空為止。

接著在leetcode中執行**,結果如下:

嗯,執行速度比較慢。

需要注意的是,上述的緩衝區可以使用鍊錶,但是注意不要覆蓋掉listnode的next屬性,否則會破壞原有的鍊錶結構。可以使用c++提供的單向鍊錶slist 或雙向鍊錶list,也可以自己封裝。執行速度應該會有所提公升。

既然可以使用排序演算法,那麼堆排序也值得一試。

void makeheap(vector& list, int len)
把list陣列調整成最小堆。

/*向下調整*/

void heapadjustdown(vector& list, int s, int length)

//回寫

list[s] = temp;

}

當棧頂元素被替換後,堆被破壞,將棧頂元素向下調整使其繼續保持最小堆的性質,再輸出棧頂元素。

而向上調整則是在將新節點放在了堆的末端,再執行的向上調整,由於本次的替換侷限於堆頂,所以用不到向上調整的函式。

listnode* mergeklists(vector& lists) 

//調整為最小堆

makeheap(heap, heap.size() - 1);

while (heap.size() > 1)

if (heap.size() != 1)

heapadjustdown(heap, 1, heap.size() - 1);

} return head->next;

}

由於為了使用到完全二叉樹的特性,所以堆的有效節點是從1開始存放的。

此時的迴圈條件改為了heap.size() > 1,和之前的queue.empty()作用是相同的。

if (heap.size() != 1)

heapadjustdown(heap, 1, heap.size() - 1);

需要注意的是,當堆裡面不存在有效資料的時候,則不再對堆調整。

接著在leetcode上執行:

幾乎相同的**,第一次跑了30ms,第二次跑了50多ms。不管是哪乙個,都要比之前的插入排序快了10倍多一點。 

當然,在筆試或者是面試的時候,手擼最小堆或最大堆演算法還是有一定難度的,因此,除了上面的做法之外,還有一種做法就是使用c++的庫中所提供的方法,其**大致如下:

class solution 

//避免煉表頭節點判斷

listnode* phead = new listnode(0);

listnode* p = phead;

//構建堆

make_heap(heap.begin(), heap.end(), compare);

while (!heap.empty())

}auto result = phead->next;

delete phead;

return result;

}private:

static bool compare(listnode* node1, listnode* node2)

};

主要用到的就是make_heap、push_heap和pop_heap,經過leetcode測試,消耗時間如下:

分治實現LeetCode 23題 合併K個排序鍊錶

紀念第一道沒有看題解做出來的困難題 分治思想 歸併排序實現合併k個排序鍊錶 由於是實現一連串已經排序的鍊錶,所以第一時間想到了歸併排序 又由於是實現多個鍊錶的總排序,為了減少時間複雜度又想到了分治思想,這一題中也就是二分法 做了這麼多天的題總算有點進步 class solution 結果陣列的頭結點...

Leetcode 合併k個鍊錶

1 分治遞迴思想 時間複雜度nlogk,空間複雜度,執行用時 88 ms,在所有 cpp 提交中擊敗了33.27 的使用者 記憶體消耗 29.7 mb,在所有 cpp 提交中擊敗了5.09 的使用者 class solution else ptr ptr next if l1 if l2 retur...

Leetcode題目60 第k個排列

給出集合 1,2,3,n 其所有元素共有 n 種排列。按大小順序列出所有排列情況,並一一標記,當 n 3 時,所有排列如下 123 132 213 231 312 321 給定 n 和 k,返回第 k 個排列。說明 給定 n 的範圍是 1,9 給定 k 的範圍是 1,n 示例 輸入 n 3,k 3 ...