LeetCode 11 水池蓄水問題


今天給大家分享的是一道LeetCode中等難度的題,難度不大,但是解法蠻有意思。我們一起來看題目:


Container With Most Water

Difficulty

Medium


題意


給定n個非負整數,表示水庫當中隔板的高度每兩塊隔板之間的距離為1,當下要從n個隔板當中選出兩個,在其中注水,並且要使得容納的水盡量多。請問最多能容納多少水?可以忽略隔板的寬度,將水庫看成是正規的長方體。


樣例:


Input: [1,8,6,2,5,4,8,3,7]
Output: 49

題解:


由於水庫可以看成是正規的長方體,所以水庫的體積可以簡化為橫截面積。也就是說我們要選擇兩個隔板,使得隔板之間圍成的矩形面積最大。


首先思考暴力求解,我們只需要枚舉矩形的兩邊,兩邊有了之后,矩形的長,也就是兩邊之間的距離,矩形的寬就是兩邊的較小值,所以復雜度是\(O(n^2)\)


這樣寫的代碼也很簡單,只有幾行:


for i in range(n):
  for j in range(i+1, n):
    ret = max(ret, (j-i) * min(a[i], a[j]))
return ret

我們來思考怎么優化,顯然大部分情況下\(O(n^2)\)的算法往往都不是最優解。考慮一個很簡單的問題,為什么取最左邊和最右邊的隔板不行呢?這樣不是矩形的長最長么?


不行的原因很簡單,因為矩形的長最長的時候,但是矩形的寬不一定很寬。有可能這樣的寬很短,就像上面圖中展示的一樣。如果這時候的結果不是最佳值,那么最佳答案的長一定小於n。如果我們用i和j指代最優解的左右兩邊的下標,那么顯然有1 <= i < j <= n。


也就是說i, j 的位置應該在1, n的內部。我們可以想象一開始的時候i指向1,j指向n,然后逐漸移動到了最優解的位置。我們應該移動i和j,但是每次應該怎么移動呢?究竟是移動i還是移動j呢?


其實稍微想一下就能想到答案,應該移動i和j兩個當中隔板比較短的那個。假設i的隔板長度小於j,即使移動i,即使碰到了更長的隔板,面積也不會變大,因為j的長度並沒有變,它依舊是短板。所以我們只有移動其中較短的那個,才有讓矩形面積變大的可能。


如果i和j的長度一樣怎么辦?答案是隨便移動哪個都一樣,有些同學可能還有顧慮。我們不妨使用一下我們之前介紹貪心算法的時候提到的均等假設法。有忘記的同學可以點擊下方的鏈接回顧一下:


貪心算法與均等假設法


我們來舉個例子,假設水庫隔板的情況是:


5 10 X .. X 4 5

我們一開始的時候i指向左邊的5,j指向右邊的5,這時候i和j相等。如果移動左邊的i,到10,面積並沒有變大。接下來會一直移動右邊的j,直到j遇到大於10的為止。並不會出現影響正確結果的情況。如果移動右邊的j呢?其實也是一樣的,因為如果j沒有遇到大於5的元素,無論左邊指向什么地方,面積都不會增大。當j遇到5以上的數的時候,必然會移動左邊的i,一樣可能增大面積。


5 10 X .. X 11 5

我們再看這個例子,如果10和11圍成的結果是正確答案,那么不論先移動i還是先移動j都是一樣的。本質上來說,如果矩形面積要增大,必須要i和j同時指向比當前更大的元素才行,所以先移動哪個並不會影響結果。


這樣一來,寫代碼就方便了,我們可以人為規定如果出現相等,就移動i。寫出來代碼如下:


i, j = 0, n-1
ret = 0
while i < j:
  ret = max(ret, (j - i) * min(a[i], a[j]))
  if a[i] <= a[j]:
    i += 1
  else:
    j -= 1
return ret

這道題既可以認為是貪心算法,也可以認為是兩指針維護區間的問題。不論怎么樣解釋,寫出來的代碼是一樣的,我個人覺得還是很巧妙的,很適合初學者練手,並且難度也不是很大。希望大家都能領會。


今天的文章就到這里,如果覺得有所收獲,請順手點個關注吧,你們的支持是我最大的動力。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM