1. 程式人生 > >每天一道LeetCode-----計算一個直方圖空隙的容量(如果裝水能裝多少)

每天一道LeetCode-----計算一個直方圖空隙的容量(如果裝水能裝多少)

Trapping Rain Water

原題連結Trapping Rain Water
這裡寫圖片描述
給定一序列,表示一個直方圖每個柱的高度(圖中黑色部分),計算這個直方圖可以儲存的容量(圖中淺藍色部分)

直觀的想法可能是直接求容量,比如說計算高度為2和高度為3的兩個柱子之間的容量,但是需要減去二者之間的其他柱子所佔的面積,比較麻煩。

從圖中可以看出,對於某個柱子,判斷它是否被某兩個柱子構成的區域包含的方法,是判斷這個柱子左右兩邊是否都存在比它高的柱子。比如說從左邊開始的那個高度為2的柱子,它右邊有一個高度為3的柱子,但是左邊沒有比它再高的柱子了,所以,它不在任兩個柱子構成的區域中。

那麼可以這樣理解,判斷一個柱子是否在某兩個柱子構成的區域中的方法,是判斷它的正上方是否有淺藍色區域。
相反,判斷一個柱子正上方是否有淺藍色方塊,只需要判斷這個柱子的左邊和右邊是否有高度大於它的柱子。而計算正上方淺藍色方塊個數的方法,是計算這個柱子左邊高度最大的柱子和右邊高度最大的柱子中高度較小的那個柱子的高度和這個柱子的高度差。

而如果垂直x軸的方向看每個柱子正上方的淺藍色方塊,可以發現整個容量恰好是由每個柱子正上方的淺藍色方塊組成(包括高度為0的柱子)。

綜上,只需要求每個柱子正上方淺藍色區域塊的個數(因為面積是1),而淺藍色區域塊的個數是通過左邊最高高度和右邊最大高度中較小的那個高度和當前柱子的高度作差得到。

直接求解

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int n = height.size();
        for(int i = 1
; i < n - 1; ++i) { int max_left = 0; int max_right = 0; /* 找當前柱子左邊的最大高度,算上當前柱子的高度 */ /* 即如果左邊沒有大於當前柱子的高度,那麼最大高度就是當前柱子的高度 */ for(int j = i; j >= 0; --j) max_left = std::max(max_left, height[j]); /* 找右邊的最大高度,同理 */
for(int j = i; j < n; ++j) max_right = std::max(max_right, height[j]); /* 兩個高度中較小的高度和當前柱子的高度差 */ /* 如果max_left == height[i],那麼差為0,上方沒有淺藍色塊 */ ans += std::min(max_left, max_right) - height[i]; } return ans; } };

動態規劃
上面的方式重複計算了很多max_left和max_right,可以用動態規劃的思想減少重複計算

class Solution {
public:
    int trap(vector<int>& height) {
        if(height.empty())
            return 0;
        int n = height.size();
        /* 對於某個位置i,計算i左邊的最大高度 */
        vector<int> left_dp(n, 0);
        left_dp[0] = height[0];
        for(int i = 1; i < n; ++i)
            left_dp[i] = std::max(left_dp[i - 1], height[i]);

        /* 對於某個位置i,計算i右邊的最大高度 */
        vector<int> right_dp(n, 0);
        right_dp[n - 1] = height[n - 1];
        for(int i = n - 2; i >= 0; --i)
            right_dp[i] = std::max(right_dp[i + 1], height[i]);

        int ans = 0;
        for(int i = 1; i < n - 1; ++i)
            ans += std::min(left_dp[i], right_dp[i]) - height[i];
        return ans;
    }
};

使用兩個指標
動態規劃的時間複雜度為O(n),空間複雜度也是O(n)。如果要將空間複雜度降到O(1),就需要實時更新max_left和max_right。
可以通過從兩邊同時向中間逼近的方法,但需要保證

  • 從左邊開始遍歷到某個柱子時,右邊一定有某個柱子的高度大於max_left。此時二者的較小值為max_left
  • 從右邊開始遍歷到某個柱子時,左邊一定有某個柱子的高度大於max_right。此時二者的較小值為max_right

要保證右邊一定有某個柱子高度大於max_left,只需要保證將max_left更改為height[left]時,height[left] < height[right]
要保證左邊一定有某個柱子高度大於max_right,只需要保證將max_right更改為height[right]時,height[right] < height[left]

所以,可以嘗試著根據height[left]和height[right]大小關係來判斷當前是應該從左向右逼近還是從右向左逼近,即

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        int ans = 0;
        int left = 0;
        int right = n - 1;
        int max_left = 0;
        int max_right = 0;
        while(left <= right)
        {
            if(height[left] < height[right])
            {
                /* 在left右邊一定有某個柱子的高度大於max_left,所以較小的就是max_left
                 * 如果當前柱子的高度大於max_left,說明左邊沒有比它高的柱子,淺藍色區域塊個數為0,此時更新max_left 
                 */
                (max_left < height[left]) ? (max_left = height[left]) : ans += (max_left - height[left]);
                ++left;
            }
            else
            {
                /* 同理 */
                (max_right < height[right]) ? (max_right = height[right]) : ans += (max_right - height[right]);
                --right;
            }
        }
        return ans;
    }
};

這題主要是弄清楚如何使用簡便的方法求出容量,即通過正上方的淺藍色區域塊的個數求解。同時,也需要明白如何求解淺藍色區域塊的個數,即找到左右兩邊最大的高度中較小的那個和當前柱子高度作差。