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