根据身高重建队列
思路
本題有兩個維度,h和k,看到這種題目一定要想如何確定一個維度,然后在按照另一個維度重新排列。
其實如果大家認真做了貪心算法:分發糖果,就會發現和此題有點點的像。
在貪心算法:分發糖果的時候說過遇到兩個維度權衡的時候,一定要先確定一個維度,再確定另一個維度。
「如果兩個維度一起考慮一定會顧此失彼」。
對于本題令人困惑的點是先確定k還是先確定h呢,也就是究竟先按h排序呢,還先按照k排序呢?
如果按照k來從小到大排序,排完之后,會發現k的排列并不符合條件,身高也不符合條件,兩個維度哪一個都沒確定下來。
那么按照身高h來排序呢,身高一定是從大到小排(身高相同的話則k小的站前面),讓高個子在前面。
「此時我們可以確定一個維度了,就是身高,前面的節點一定都比本節點高!」
那么只需要按照k為下標重新插入隊列就可以了,為什么呢?
以圖中{5,2} 為例:
按照身高排序之后,優先按身高高的people的k來插入,后序插入節點也不會影響前面已經插入的節點,最終按照k的規則完成了隊列。
所以在按照身高從大到小排序后:
「局部最優:優先按身高高的people的k來插入。插入操作過后的people滿足隊列屬性」
「全局最優:最后都做完插入操作,整個隊列滿足題目隊列屬性」
局部最優可推出全局最優,找不出反例,那就試試貪心。
回歸本題,整個插入過程如下:
排序完的people:
[[7,0], [7,1], [6,1], [5,0], [5,2],[4,4]]
插入的過程:插入[7,0]:[[7,0]]
插入[7,1]:[[7,0],[7,1]]
插入[6,1]:[[7,0],[6,1],[7,1]]
插入[5,0]:[[5,0],[7,0],[6,1],[7,1]]
插入[5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
插入[4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
此時就按照題目的要求完成了重新排列。
時間復雜度O(nlogn + n^3) 空間復雜度O(n) class Solution { public:static bool cmp(vector<int> a,vector<int> b){if(a[0]==b[0]) return a[1]<b[1]; //身高相同的,返回下標小的,升序排列else return a[0]>b[0];//否則按身高降序排列//要點看比較符號<表明要升序排列,>表示要降序排列}vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {sort(people.begin(),people.end(),cmp);vector<vector<int>> que;for(int ii=0;ii<people.size();ii++){int position=people[ii][1];que.insert(que.begin()+position,people[ii]);} return que;} };n^3 是怎么來的?
數組中插入元素和刪除元素都是O(n^2)的復雜度。
我們就是要模擬一個插入隊列的行為,所以不應該使用數組,而是要使用鏈表!
鏈表的插入操作復雜度是O(n):尋找插入元素位置O(n),插入元素O(1)。
可以看出使用鏈表的插入效率要比普通數組高出一個數量級!
// 版本二 時間復雜度O(nlogn + n^2) 空間復雜度O(n) class Solution { public:// 身高從大到小排(身高相同k小的站前面)static bool cmp(const vector<int> a, const vector<int> b) {if (a[0] == b[0]) return a[1] < b[1];return a[0] > b[0];}vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {sort (people.begin(), people.end(), cmp);list<vector<int>> que; // list底層是鏈表實現,插入效率比vector高的多for (int i = 0; i < people.size(); i++) {int position = people[i][1]; // 插入到下標為position的位置std::list<vector<int>>::iterator it = que.begin();while (position--) { // 尋找在插入位置it++; }que.insert(it, people[i]); }return vector<vector<int>>(que.begin(), que.end());} };總結
關于出現兩個維度一起考慮的情況,我們已經做過兩道題目了,另一道就是貪心算法:分發糖果。
「其技巧都是確定一邊然后貪心另一邊,兩邊一起考慮,就會顧此失彼」。
這道題目可以說比貪心算法:分發糖果難不少,其貪心的策略也是比較巧妙。
最后我給出了兩個版本的代碼,可以明顯看是使用C++中的list(底層鏈表實現)比vector(數組)效率高得多。
「對使用某一種語言容器的使用,特性的選擇都會不同程度上影響效率」。
總結
- 上一篇: 分发糖果!
- 下一篇: 用最少数量的箭引爆气球