3種解法 - 計算盛最多水的容器


題目

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


解法一(暴力法)

思路:直接兩層遍歷,分別計算任意兩個桶之間的容積,然後記錄最大的結果,作爲中等難度的題目,這個方法可能會超時,最終不出所料在最後一個測試用例超時了,超時用例爲15000個從大到小排列的數值,測試用例及源碼如下:
https://www.zhenxiangsimple.com/files/tech/testCase20200208.txt

  1. 第一層遍歷作爲桶的左側,第二層遍歷,作爲桶的右側
  2. 選擇較小的值乘以間距,計算桶的容積
  • 時間複雜度:O(n2)
  • 空間複雜度:O(1)
public class Solution {
    public int MaxArea(int[] height) {
        int t,mx=0;
        for(int i=0;i<height.Length;i++)
        {//桶左側
            t = 0;
            for(int j=i+1;j<height.Length;j++)
            {//桶右側
                t = (j-i) * (height[i]>height[j]?height[j]:height[i]);//計算容積
                if(t > mx)
                {
                    mx = t;
                }
            }
        }
        return mx;
    }
}

解法二(從外向裏)

思路:用兩個指針分別從兩邊向中間進行查找,如果全部遍歷就跟暴力法一樣了,本算法使用較小的值向較大值的方向移動,因爲X座標變小後只有Y座標變大才可能變得更大,因此選擇捨棄較小(短)的Y值,向較大的方向移動來尋找更大的值。

  1. 分別使用左右兩個指針,從數組開始和結尾開始遍歷
  2. 值小的一邊向另一邊移動,直到兩個指針相遇
  • 時間複雜度:O(n)
  • 空間複雜度:O(1)
public class Solution {
    public int MaxArea(int[] height) {
        int l=0,r=height.Length-1,t,mx=0;
        while(l<r)
        {
            if(height[l] > height[r])
            {//右邊小,右指針向左移動
                t = height[r] * (r-l);
                r--;
            }
            else
            {//左邊小,左指針向右移動
                t = height[l] * (r-l);
                l++;
            }
            if(t>mx)
            {//記錄最大值
                mx = t;
            }
        }
        return mx;
    }
}

解法三(從裏向外)

思路:基於解法二的由外向裏的過程,自然想到從上而下的思路,首先選擇兩個最高的值,然後桶外側找最大值,因爲高度減小了只有向外才能使得容積變大。

  1. 找到最大值和次大值的索引,作爲桶的左右邊界
  2. 在桶外面找最大的一個值,記錄桶的容積,直到數組遍歷結束
  3. 這個跟解法二不同之處是,不是選其中一邊裏的做大值,而是兩邊的最大值,因爲可能由於選擇了較小的值導致拉低高度值
  • 時間複雜度:O(n2)
  • 空間複雜度:O(1)
public class Solution {
    public int findMaxIdx(int[] height,int l,int r)
    {  //尋找[l,r)內最大索引
        int ti = l;
        for(int i=l + 1;i<r;i++)
        {
            if(height[i] > height[ti])
            {
                ti = i;
            }
        }
        return ti;
    }

    public int findOutIdx(int[] height,int l,int r,out bool lFlag)
    {//尋找[l,r]外面最大索引
        lFlag = true;
        int li = -1;
        if(l > 0)
        {
            li = findMaxIdx(height,0,l);
        }

        if(r < height.Length - 1) 
        {           
            int ri = findMaxIdx(height,r+1,height.Length);
            if(li==-1 || height[li] < height[ri])
            {
                lFlag = false;
                li = ri;
            }
        }
        return li;
    }

    public int MaxArea(int[] height) {
        int t,mx=0,len = height.Length;
        int li=0,ri=-1,ti,tr=-1;
        bool flag;
        for(int i=1;i<len;i++)
        {//li最大值,ri次大值
            if(height[i] > height[li])
            {//比最大值還大
                tr = height[li];
                ri = li;
                li = i;
            }
            else if(height[i] > tr)
            {//介於中間
                tr=height[i];
                ri = i;
            }
        }
        if(li > ri)
        {//li爲左邊界,ri爲右
            t = li;
            li = ri;
            ri = t; 
        }
        mx = (height[ri] > height[li]?height[li]:height[ri]) * (ri-li);
        while(ri<len-1 || li>0)
        {//交替切換
            ti = findOutIdx(height,li,ri,out flag);
            if(flag)
            {//向左拓寬了
                li = ti;
            }
            else
            {//向右
                ri = ti;
            }
            t = (height[ri] > height[li]?height[li]:height[ri]) * (ri-li);
            mx = t>mx?t:mx;
        }
        return mx;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章