題目:盛最多水的容器
給你 n 個非負整數 a1,a2,…,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別爲 (i, ai) 和 (i, 0)。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器,且 n 的值至少爲 2。
圖中垂直線代表輸入數組 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示爲藍色部分)的最大值爲 49。
示例:
輸入:[1,8,6,2,5,4,8,3,7]
輸出:49
道人第一想法就是暴力求解
這裏道人說明下,暴力求解並沒什麼不好;道人解釋下原因和自己的理解。
- 首先:計算機就是做運算的,就是擅長做重複的邏輯計算,以現在計算機的性能,一些在你眼中的高時間複雜度的工作,也許效率並不是你想象的那麼低。
- 如果說第一個原因你說有些牽強,那麼下面兩個原因,也許能爲暴力求解找出合理的解釋。
- 數據量不大:在業務場景(數據量不大)的情況下,O(n)與O(n二次方)的差距並不大。
- 代碼可讀性強:相對而言,暴力求解算是很容易被不同的人理解的。
- 方便理清邏輯:暴力求解,其實是幫我們理清最基礎的邏輯,在理清基礎邏輯後,如何優化?如何轉換角度?如何提高性能?暴力求解都給我們奠定了優化該題的基礎。
暴力求解:任意兩點(ai,aj)的容器容積的計算公式Math.min(ai,aj)*(j-i)。
代碼如下
class Solution {
public int maxArea(int[] height) {
// 暴力求解
int maxArea = 0;
for (int i = 0; i < height.length; i++){
for(int j = i+1; j < height.length; j++){
if (Math.min(height[i],height[j])*(j-i) > maxArea){
maxArea = Math.min(height[i],height[j])*(j-i);
}
}
}
return maxArea;
}
}
第二思路: 雙向指針(排除了很多明顯小於當前容積的情況)
核心:選取最左、最右兩個指針,實際上是獲取了以較小高度爲一邊的所有容器的最大容積!!!!剔除了中間的很多明顯小於當前容積的情況!!!
從頭到尾遍歷必須再次嵌套一層循環,那麼從尾到頭遍歷啦?或者雙向指針一起遍歷?會是什麼結果。
分析思路:
- 不難發現暴力求解存在着大量的重複計算。
以i=0該點爲例,假若height[i]爲較低的一點,那麼高度則以height[i]爲基準,最大容積與長度相關則height[height.length-1]爲構成的最大容積(以i=0該點爲一邊的容積)。 - 若我們以height左右兩邊爲起點,以兩邊較小值爲一邊,便是此時最大容積;**那麼怎麼移動纔可能使下一個容積大於當前容積?**較大一邊的指針向中間移動?由於底邊減少,那麼容積要想大於原來的值,高度必須大於原高度,那麼由於移動的較大一邊的指針,所以此時高度由原來的較小高度一邊限制,因此不可能大於原容積。
- 所以,移動兩邊中較小高度一邊的指針,纔有可能在接下來的比較中獲取到比原來更大的容積。
上述邏輯的演示:(記住一點只有較小高度一邊的指針移動,新的容器值纔可能大於上一個容器值;同時你每次獲取的均是以較小邊爲容器一邊的最大值)
代碼如下:
class Solution {
public int maxArea(int[] height) {
// 雙指針求解法
int maxArea = 0;
int i = 0;
int j = height.length-1;
while( j > i){
// 計算當前的最大容器值
maxArea = Math.max(Math.min(height[j],height[i])*(j-i),maxArea);
// 較小一邊的指針移動
if (height[j] > height[i]){
i++;
} else {
j--;
}
}
return maxArea;
}
}