很多演算法問題都需要排序技巧,其難點不在於排序本身,而是需要巧妙地排序進行預處理,將演算法問題進行轉換,為之後的操作打下基礎。
信封巢狀問題就需要先按特定的規則排序,之後就轉換為乙個 最長遞增子串行問題,可以用前文 二分查詢詳解 的技巧來解決了。
信封巢狀問題是個很有意思且經常出現在生活中的問題,先看下題目:
這道題目其實是最長遞增子串行(longes increasing subsequence,簡寫為 lis)的乙個變種,因為很顯然,每次合法的巢狀是大的套小的,相當於找乙個最長遞增的子串行,其長度就是最多能巢狀的信封個數。
但是難點在於,標準的 lis 演算法只能在陣列中尋找最長子序列,而我們的信封是由(w, h)
這樣的二維數對形式表示的,如何把 lis 演算法運用過來呢?
讀者也許會想,通過w × h
計算面積,然後對面積進行標準的 lis 演算法。但是稍加思考就會發現這樣不行,比如1 × 10
大於3 × 3
,但是顯然這樣的兩個信封是無法互相巢狀的。
這道題的解法是比較巧妙的:
先對寬度w
進行公升序排序,如果遇到w
相同的情況,則按照高度h
降序排序。之後把所有的h
作為乙個陣列,在這個陣列上計算 lis 的長度就是答案。
畫個圖理解一下,先對這些數對進行排序:
然後在h
上尋找最長遞增子串行:
這個子串行就是最優的巢狀方案。
這個解法的關鍵在於,對於寬度w
相同的數對,要對其高度h
進行降序排序。因為兩個寬度相同的信封不能相互包含的,逆序排序保證在w
相同的數對中最多隻選取乙個。
下面看**:
// envelopes = [[w, h], [w, h]...]
public int maxenvelopes(int envelopes)
});// 對高度陣列尋找 lis
int height = new int[n];
for (int i = 0; i < n; i++)
height[i] = envelopes[i][1];
return lengthoflis(height);
}
/* 返回 nums 中 lis 的長度 */
public int lengthoflis(int nums)
if (left == piles) piles++;
// 把這張牌放到牌堆頂
top[left] = poker;
}// 牌堆數就是 lis 長度
return piles;
}
為了清晰,我將**分為了兩個函式, 你也可以合併,這樣可以節省下height
陣列的空間。
此演算法的時間複雜度為 \(o(nlogn)\),因為排序和計算 lis 各需要 \(o(nlogn)\) 的時間。
空間複雜度為 \(o(n)\),因為計算 lis 的函式中需要乙個top
陣列。
這個問題是個 hard 級別的題目,難就難在排序,正確地排序後此問題就被轉化成了乙個標準的 lis 問題,容易解決一些。
其實這種問題還可以拓展到三維,比如說現在不是讓你巢狀信封,而是巢狀箱子,每個箱子有長寬高三個維度,請你算算最多能巢狀幾個箱子?
我們可能會這樣想,先把前兩個維度(長和寬)按信封巢狀的思路求乙個巢狀序列,最後在這個序列的第三個維度(高度)找一下 lis,應該能算出答案。
實際上,這個思路是錯誤的。這類問題叫做「偏序問題」,上公升到三維會使難度巨幅提公升,需要借助一種高階資料結構「樹狀陣列」,有興趣的讀者可以自行搜尋。
信封巢狀問題
給n個信封的長度和寬度。如果信封a的長和寬都小於信封b,那麼信封a可以放到信封b裡,請求出信封最多可以巢狀多少層。輸出包含多行,第一行包括乙個整數,代表信封的個數n 1 n 100000 接下來n行,每行兩個整數li和wi 代表信封的長度和寬度 1e9 include includeusing na...
信封巢狀問題
354.俄羅斯套娃信封問題 很多演算法問題都需要排序技巧,其難點不在於排序本身,而是需要巧妙地排序進行預處理,將演算法問題進行轉換,為之後的操作打下基礎。信封巢狀問題就需要先按特定的規則排序,之後就轉換為乙個 最長遞增子串行問題 的技巧來解決了。信封巢狀問題是個很有意思且經常出現在生活中的問題,先看...
listview巢狀問題
publicstaticvoidsetlistviewheightbasedonchildren listview listview inttotalheight 0 for inti 0,len listadapter.getcount i len i viewgroup.layoutparams...