close

question_11.jpg

來源:Container With Most Water - LeetCode

【leetcode】Container With Most Water 講解

問題:

  如標題,這篇文章是要講解 leetcode 的演算法題目。講解的原因在於我認為 Container With Most Water 是一個很不錯的經典題目,解題者不需要使用高難度的資料結構或演算法技巧,只需正確理解問題本身特性之後便可以大幅度降低時間複雜度。

 

You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]).

Find two lines that together with the x-axis form a container, such that the container contains the most water.

Return the maximum amount of water a container can store.

 

question_11.jpg

來源:Container With Most Water - LeetCode

  問題本身描述很簡單,找出擁有最大蓄水量的邊組合 (兩個邊)。最簡單的方法當然就是暴力法,使用雙迴圈嘗試所有邊組合,不過這樣很顯然會超時,所以接下來就讓我們談談如何將時間複雜度降至 O(n)。

 

解答:

  首先讓我們仔細看看問題所要求的最大蓄水量,該蓄水量與寬度與高度成正比。

 

situation1.png

  如果當下有兩個邊,他們中間的邊高度都低於兩者較低者,那代表中間不存在擁有更大蓄水量的邊組合。因為兩個邊中間寬度必定更窄,當高度也更低時就不可能存在更大的蓄水量。

 

situation2_1.png

  反之,如果當下有兩個邊,他們中間的高度有高於兩者較低者的邊存在,那中間就有可能出現擁有更大蓄水量的邊組合。

 

situation2_2.png

  由於兩個邊中間寬度必定更窄,導致蓄水量僅由高度低的邊決定,這時候我們只需要每次移動較低的一邊並計算當下的蓄水量,最後輸出最大值即可找到這個區間內最大的蓄水量。

 

  所以我們可以得出一個時間複雜度為 O(n) 的方法:

  • 從最左與最右的兩個邊開始
  • 每次移動較低的一邊並計算當下的蓄水量
  • 當兩個邊碰頭時即結束,輸出最大值即可找到最大的蓄水量。

 

class Solution {
public:
    int maxArea(vector<int>& height) {
        int maxWaterArea = 0;
        int left = 0;
        int right = height.size() - 1;
       
        while (left < right) {
            int minHeight = min(height[left], height[right]);
            int nowWaterArea = (right - left) * minHeight;
            maxWaterArea = max(maxWaterArea, nowWaterArea);
            if (height[right] < height[left]) {
                right = right - 1;
            } else {
                left = left + 1;
            }
        }
        return maxWaterArea;
    }
};

 

example1_1.png

example1_2.png

  這個方法也被稱作 two pointer,我們只需要紀錄兩個指標以及最大蓄水量,每次根據長短狀況將他們其中一個往中間移動,當所有邊都被看過時,問題便被解決。

 

常見疑惑:

  這個這個方法時常會讓人感到疑惑,在 leetcode 上也有許多討論

 

confuse.png

  常見的疑惑如上圖,依照 two pointer 方法我們最終會走到 A 組合,但我們完全沒看過 B 組合,是在什麼時候確定 B 組合一定不會更大的?two pointer 是在什麼時候判定那些沒看過的邊組合中不存在更佳解的?

 

  該疑惑的解答其實就是先前一直出現的觀念,two pointer 下兩個邊中間寬度必定更窄,導致蓄水量僅由高度低的邊決定。

 

answer.png

  長的一邊不論怎麼替換成什麼高度都無法再增加蓄水量,寬度只會越來越窄、高度永遠無法超過低邊的 2。反過來說替換掉短的一邊,雖然寬度一樣變窄,但如果遇到更高邊就有機會獲得更多的容量。所以我們完全不需要考慮移動長邊所產生的組合 (包含 B 組合),它們之中不可能存在更大的蓄水量。這就是 B 組合可以直接被放棄不需計算的原因。

 

結語:

  我覺得這個題目很有意思才特別寫在部落格,能夠應用 two pointer 來消除掉大量排列組合,是源自於題目本身蓄水量的計算與寬度長度成正比,當其中一方被 two pointer 方法控制住就很容易判斷另一方是否還需要計算。在真實生活也是,我們越是了解問題的本質或特性,就能排除不可能的選項來加速計算甚至直接找出最佳解公式。

 

參考資料:

[1] Container With Most Water - LeetCode

https://leetcode.com/problems/container-with-most-water/

[2] Yet another way to see what happens in the O(n) algorithm - LeetCode Discuss

https://leetcode.com/problems/container-with-most-water/discuss/6099/Yet-another-way-to-see-what-happens-in-the-O(n)-algorithm

 

arrow
arrow
    創作者介紹
    創作者 迷宮兔 的頭像
    迷宮兔

    兔窩

    迷宮兔 發表在 痞客邦 留言(0) 人氣()