每天 3 分鐘,走上算法的逆襲之路。
前文合集
代碼倉庫
GitHub: https://github.com/meteor1993/LeetCode
Gitee: https://gitee.com/inwsy/LeetCode
題目:盛最多水的容器
難度:中等
題目來源:https://leetcode-cn.com/problems/container-with-most-water/
給你 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
解題思路
今天這道題蠻有意思的,給出一個數組,實際上是在求這個數組之間可以組成的最大面積。
這道題正統的解法是使用雙指針,因爲是在數組之間灌水,能容納的水有多少是取決於那個最小的數字,所以我們先把兩個指針分別指向數組的頭和尾,先求一下當前的面積,然後比較下頭尾的大小,每次移動較小的那個指針,把整個數組迭代一遍,取出其中的最大值即可。
public int maxArea(int[] height) {
int len = height.length;
int start = 0, end = len - 1;
int maxArea = 0;
while (start != end) {
int area = Math.min(height[start], height[end]) * (end - start);
maxArea = Math.max(area, maxArea);
if (height[start] < height[end]) {
start++;
} else {
end--;
}
}
return maxArea;
}
代碼看起來不難,聲明一點哈,上面這個方案不是我想出來的,是看了答案的提示以後才知道的。
但是這裏面有個問題,爲什麼這種方案是可以找到最大的面積的,難道就不會存在其他的情況麼?
下面的這個證明方案同樣來自於答案當中,用來證明上面這個方案的正確性。
雙指針代表的是可以作爲容器邊界的所有位置的範圍。在一開始,雙指針指向數組的左右邊界,表示數組中所有的位置都可以作爲容器的邊界,因爲我們還沒有進行過任何嘗試。
在這之後,我們每次將對應的數字較小的那個指針往另一個指針 方向移動一個位置,就表示我們認爲這個指針不可能再作爲容器的邊界了。
那麼爲啥這個指針不能在作爲容器的邊界了,下面是這個問題的證明:
首先考慮第一步,假設當前左指針和右指針指向的數分別爲 x 和 y,先假設 x < y ,同時兩個指針之間的距離爲 t ,那麼他們組成的容器的容量爲:
min(x, y) * t = x * t
這時可以斷定,如果我們保持左指針的位置不變,那麼無論右指針在哪裏,這個容器的容量都不會超過 x * t 了。注意這裏右指針只能向左移動,因爲我們考慮的是第一步,也就是指針還指向數組的左右邊界的時候。
我們任意的移動右指針,指向的數字爲 y1 ,兩個指針之間的距離爲 t1 ,那麼顯然有 t1 < t ,並且 min(x, y1) <= min(x, y)
。
- 如果 y1 <= y 那麼有
min(x, y1) <= min(x, y)
。 - 如果 y1 > y 那麼有
min(x, y1) = x <= min(x, y)
。
因此剛纔上面的那個公式可推導:
min(x, y1) * t1 = min(x, y) * t
即無論我們怎麼移動右指針,得到的容器的容量都小於移動前容器的容量。也就是說,這個左指針對應的數不會作爲容器的邊界了,那麼我們就可以丟棄這個位置,將左指針向右移動一個位置,此時新的左指針於原先的右指針之間的左右位置,纔可能會作爲容器的邊界。
這樣以來,我們將問題的規模減小了 11,被我們丟棄的那個位置就相當於消失了。此時的左右指針,就指向了一個新的、規模減少了的問題的數組的左右邊界,接下來,我們可以重複上面的這個過程,接着縮小容器邊界。