題目
給你 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]
^ ^
在初始時,左右指針分別指向數組的左右兩端,它們可以容納的水量爲 \min(1, 7) * 8 = 8min(1,7)∗8=8。
此時我們需要移動一個指針。移動哪一個呢?直覺告訴我們,應該移動對應數字較小的那個指針(即此時的左指針)。這是因爲,由於容納的水量是由
兩個指針指向的數字中較小值 * 指針之間的距離
兩個指針指向的數字中較小值∗指針之間的距離
決定的。如果我們移動數字較大的那個指針,那麼前者「兩個指針指向的數字中較小值」不會增加,後者「指針之間的距離」會減小,那麼這個乘積會減小。因此,我們移動數字較大的那個指針是不合理的。因此,我們移動 數字較小的那個指針。
有讀者可能會產生疑問:我們可不可以同時移動兩個指針? 先別急,我們先假設 總是移動數字較小的那個指針 的思路是正確的,在走完流程之後,我們再去進行證明。
所以,我們將左指針向右移動:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量爲 \min(8, 7) * 7 = 49min(8,7)∗7=49。由於右指針對應的數字較小,我們移動右指針:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量爲 \min(8, 3) * 6 = 18min(8,3)∗6=18。由於右指針對應的數字較小,我們移動右指針:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量爲 \min(8, 8) * 5 = 40min(8,8)∗5=40。兩指針對應的數字相同,我們可以任意移動一個,例如左指針:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量爲 \min(6, 8) * 4 = 24min(6,8)∗4=24。由於左指針對應的數字較小,我們移動左指針,並且可以發現,在這之後左指針對應的數字總是較小,因此我們會一直移動左指針,直到兩個指針重合。在這期間,對應的可以容納的水量爲:\min(2, 8) * 3 = 6min(2,8)∗3=6,\min(5, 8) * 2 = 10min(5,8)∗2=10,\min(4, 8) * 1 = 4min(4,8)∗1=4。
在我們移動指針的過程中,計算到的最多可以容納的數量爲 4949,即爲最終的答案。
# cpp
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0;
int i = 0;
int j = height.size() - 1;
while (i < j) {
int area = (j - i) * min(height[i], height[j]);
res = max(res, area);
if (height[i] < height[j]) {
i++;
} else {
j--;
}
}
return res;
}
};
# python3
class Solution:
def maxArea(self, height: List[int]) -> int:
l, r = 0, len(height) - 1
ans = 0
while l < r:
area = min(height[l], height[r]) * (r - l)
ans = max(ans, area)
if height[l] <= height[r]:
l += 1
else:
r -= 1
return ans