1. 程式人生 > 實用技巧 >leetcode刷題筆記-11. 盛最多水的容器(java實現)

leetcode刷題筆記-11. 盛最多水的容器(java實現)

題目描述

給你 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

來源:力扣(LeetCode) 連結:https://leetcode-cn.com/problems/container-with-most-water

解題思路

雙指標
說明

本題是一道經典的面試題,最優的做法是使用「雙指標」。如果讀者第一次看到這題,不一定能想出雙指標的做法。

分析

我們先從題目中的示例開始,一步一步地解釋雙指標演算法的過程。稍後再給出演算法正確性的證明。

題目中的示例為:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
在初始時,左右指標分別指向陣列的左右兩端,它們可以容納的水量為 min(1, 7) * 8 = 8。

此時我們需要移動一個指標。移動哪一個呢?直覺告訴我們,應該移動對應數字較小的那個指標(即此時的左指標)。這是因為,由於容納的水量是由 兩個指標指向的數字中較小值∗指標之間的距離

決定的。如果我們移動數字較大的那個指標,那麼前者「兩個指標指向的數字中較小值」不會增加,後者「指標之間的距離」會減小,那麼這個乘積會減小。因此,我們移動數字較大的那個指標是不合理的。因此,我們移動 數字較小的那個指標。

有讀者可能會產生疑問:我們可不可以同時移動兩個指標? 先別急,我們先假設 總是移動數字較小的那個指標 的思路是正確的,在走完流程之後,我們再去進行證明。

所以,我們將左指標向右移動:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量為 min(8,7)∗7=49。由於右指標對應的數字較小,我們移動右指標:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量為 min(8,3)∗6=18。由於右指標對應的數字較小,我們移動右指標:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量為 min(8,8)∗5=40。兩指標對應的數字相同,我們可以任意移動一個,例如左指標:
[1, 8, 6, 2, 5, 4, 8, 3, 7]
^ ^
此時可以容納的水量為 min(6,8)∗4=24。由於左指標對應的數字較小,我們移動左指標,並且可以發現,在這之後左指標對應的數字總是較小,因此我們會一直移動左指標,直到兩個指標重合。在這期間,對應的可以容納的水量為:min(2, 8) * 3 = 6,min(5,8)∗2=10,min(4,8)∗1=4。

在我們移動指標的過程中,計算到的最多可以容納的數量為 49,即為最終的答案。

證明

為什麼雙指標的做法是正確的?

雙指標代表了什麼?

雙指標代表的是 可以作為容器邊界的所有位置的範圍。在一開始,雙指標指向陣列的左右邊界,表示 陣列中所有的位置都可以作為容器的邊界,因為我們還沒有進行過任何嘗試。在這之後,我們每次將 對應的數字較小的那個指標 往 另一個指標 的方向移動一個位置,就表示我們認為 這個指標不可能再作為容器的邊界了。

為什麼對應的數字較小的那個指標不可能再作為容器的邊界了?

在上面的分析部分,我們對這個問題有了一點初步的想法。這裡我們定量地進行證明。

考慮第一步,假設當前左指標和右指標指向的數分別為 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,yt)∗t1<min(x,y)∗t

即無論我們怎麼移動右指標,得到的容器的容量都小於移動前容器的容量。也就是說,這個左指標對應的數不會作為容器的邊界了,那麼我們就可以丟棄這個位置,將左指標向右移動一個位置,此時新的左指標於原先的右指標之間的左右位置,才可能會作為容器的邊界。

這樣以來,我們將問題的規模減小了 1,被我們丟棄的那個位置就相當於消失了。此時的左右指標,就指向了一個新的、規模減少了的問題的陣列的左右邊界,因此,我們可以繼續像之前 考慮第一步 那樣考慮這個問題:

求出當前雙指標對應的容器的容量;

對應數字較小的那個指標以後不可能作為容器的邊界了,將其丟棄,並移動對應的指標。

最後的答案是什麼?

答案就是我們每次以雙指標為左右邊界(也就是「陣列」的左右邊界)計算出的容量中的最大值。

(思路來源:力扣(LeetCode)連結:https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/)

解題程式碼

複雜度分析

  • 時間複雜度:O(N),雙指標總計最多遍歷整個陣列一次。

  • 空間複雜度:O(1),只需要額外的常數級別的空間。

class Solution {
    public int maxArea(int[] height) {
        int l=0,r=height.length - 1;
        int area = 0;
        while(l<r) {
            int a = Math.min(height[l],height[r])*(r-l);
            area = Math.max(area, a);
            if(height[l] > height[r]) {
                r--;
            } else {
                l++;
            }
        }
        return area;
    }
}