切木头之二分法启示
183. 木材加工
有一些原木,現在想把這些木頭切割成一些長度相同的小段木頭,需要得到的小段的數目至少為 k。當然,我們希望得到的小段越長越好,你需要計算能夠得到的小段木頭的最大長度。 木頭長度的單位是厘米。原木的長度都是正整數,我們要求切割得到的小段木頭的長度也要求是整數。無法切出要求至少 k 段的,則返回 0 即可。
解釋一下就是:給定了一個切割目標長度Len后,每一根原木都可以切割出多條,加在一起的數目totalCount要求大于等于k。在滿足總數目totalCount大于等于k的條件下,得到一個最長的目標長度len。
暴力解
首先想到的是暴力解,當len給定,totalCount就可以計算出來,而且可以知道len越小,totalCount就越大。那么從最長的原木長度maxLen開始算總數,第一個滿足totalCount>=k就是我們要的解。
時間復雜度是n*(maxLen-targetLen),隨機情況來看,每個長度概率一樣,就是n*(∑k/maxLen), 1<=k<=maxLen,即O(n*maxLen)。
這個肯定是不夠的。
動態規劃
對于最有解的問題,很自然的會想到動態規劃,而動態規劃的核心的找到大問題向小問題的轉移方式。在這一題里,也就是找到切割目標長度len時的總數和len+1時的總數之間的關系,倘若這兩者直接的計算復雜度為O(x),那么總復雜度為O(x*maxLen),那么只要x<n就可以得到優化。
有什么辦法可以讓兩個總長度直接關聯得到計算,而不需要再次循環一個個原木從新計算呢?這個我嘗試了,沒想出來。
二分法
然后看了眼題目的標簽,是二分法,瞬間感覺有戲了。 這個情況里最有用的一個分析是:目標長度len越小,總數totalCount就越大。對比一個二分查抄的邏輯,找到一個目標來分隔區間,然后不斷的縮小區間,最后剩下的是解。這里就是:一開始的選擇區間是[1,maxLen],取中數mid,求出總數,和k比較,如果總數小,那么就要繼續壓小長度,那么選擇區間就變成了[1,mid],反之選擇區間就是[mid,maxLen]。按照這樣的思路,區間不斷縮小,最后找到解。
int woodCut(vector<int> &L, int k) {int maxLen = 0;int residue = k;for (int len : L){if (residue>0) {residue -=len; //不直接比較總量是因為可能會超出int范圍}maxLen = max(maxLen, len);}//排除頭<kif (residue > 0) {return 0;}//排除尾>=kint maxLenCount = 0;for (int len : L){if (len == maxLen) {if ((++maxLenCount)==k) {return maxLen;}}}//循環不變條件是:左邊結果>=k,右邊<k。所以前面先把不滿足的頭和尾情況排除,逼近到最后left和right相鄰時,left就是解。int left = 1, right = maxLen;while (left < right-1) {int mid = left+(right-left)/2;int count = 0;for (int len : L){count += len/mid;}if (count<k) {right = mid;}else{left = mid;}}return left; } 復制代碼啟示
這題給我最大的一個啟示是:二分法的使用跟環境存在一個單調遞增或遞減的關系是緊密相關的。
假設存在兩個變量A和B,A和B的關系是單調遞增或遞減,假設為遞增,即A月大則B越大。然后我們要求B為b'時的A的值a',那么就可以用二分法了。
先來一個區間[a1, a2],只要a1和a2對應的B值是在目標的兩邊,即一個大于目標一個小于目標,那么就可以用二分法的手段不斷的壓縮區間直到最后找到解。
那如果a1,a2對應的B值在同一邊呢?那么一般這種情況下, 已經到了邊緣還沒有解,就是求最近的數,那么解就是a1或者a2。
解算法題,找到對應的解法模型問題就會很快,那怎么知道某一題對應了什么樣的解法模型,我覺得就是有一些引子、征兆之類的東西,這才是我想說的,這個切木頭題目只是個例子。對于二分法,它的一個征兆就是存在兩個變量,它們之間有單調遞減或遞增的關系。
總結
- 上一篇: Abp框架之执行Update-Datab
- 下一篇: 现成