算法基礎~鏈表~排序鏈表的合並(k條)
1,題意:已知k個已排序鏈表頭結點指針,將這k個鏈表合並,合並后仍然為有序的,返回合並后的頭結點。
2,方法之間時間復雜度的比較:
方法1(借助工具vector封裝好的sort方法):將k * n個結點放到vector,則原 vector的排序時間復雜度是 O(nlogn);
有k*n個結點的排序,時間復雜度是 O(knlog(kn));
方法2(分制后相連法),分制:這里咱要想到高中的DNA復制~一個DNA復制生成K個DNA的過程。
【1----復制----》k個數】,復制了n次,結果有k個數,則2n = k。
這里咱反過來,想到是在融合,則【k個數----復制----》1】,這個過程需要融合的次數,跟當初 復制次數一樣,由2n = k,解得,n=logk。
●具體過程分析:
第一次,鏈表兩兩之間合並,進行 合並次數為 k/2,每次處理結點數字為2n個;
第二次,鏈表兩兩之間合並,進行 合並次數為 k/4,每次處理結點數字為4n個;
。。。
由上面推導的融合過程,知道最后一次,即 第 logk 次,鏈表兩兩之間進行 合並,進行合並次數為
k/2 logk,每次處理結點數字為2 logk n個;
時間復雜度:2n * k/2 + 4n * k/4 + 8n * k/8 + … + 2 logk n * k/2 logk = nk + nk + nk +… +nk = O(nklogk);
所以方法2,更優;
3,從方法2的分析過程,咱深深的感受到一種:把規模大的問題變成規模較小的;規模較小的問題又變成規模更小的問題,
小到一定程度可以直接得出它的解,從而得到問題的解。~沒錯,是遞歸的味道!
4,直接上代碼,分析如上【代碼中的mergeTwoLists(鏈表1頭指針,鏈表2頭指針)參考咱上一篇文章:
《算法基礎~鏈表~排序鏈表的合並(2條)》~ https://www.cnblogs.com/shan333/p/15041561.html】:
public class Solution { public: ListNode* mergeKLists(std::vector<ListNode*>& lists){ if(lists.size() == 0){ return NULL; } if(lists.size() == 1){ return lists[0]; } if(lists.size() == 2){ return mergeTwoLists(lists[0],lists[1]); } int mid = lists.size() / 2; //拆分成兩個子lists std::vector<ListNode*> sub1_lists; std::vector<ListNode*> sub2_lists; for(int i = 0; i < mid; i++){ sub1_lists.push_back(lists[i]); } for(int i = mid; i < lists.size(); i++){ sub2_lists.push_back(lists[i]); } //遞歸,不斷的兩兩鏈表進行融合 ListNode *l1 = mergeKList(sub1_lists); ListNode *l2 = mergeKList(sub2_lists); return mergeTwoLists(l1, l2); } }
5,遞歸思想的使用:
遞歸算法的思想是:把規模大的問題變成規模較小的;規模較小的問題又變成規模更小的問題,小到一定程度可以直接得出它的解,從而得到問題的解。
解決問題時,把一個問題轉化為一個新的問題,而這個新的問題的解決方法仍與原問題的解法相同,
只是所處理的對象有所不同,這些被處理的對象之間是有規律的遞增或遞減;
參考文章:《什么情況下用遞歸?~https://blog.csdn.net/ggxxkkll/article/details/7524056》