1. 程式人生 > 其它 >#力扣 LeetCode1046. 最後一塊石頭的重量 @FDDLC

#力扣 LeetCode1046. 最後一塊石頭的重量 @FDDLC

技術標籤:LeetCode

42. 接雨水

題目描述

給定 n 個非負整數表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。

示例1:
在這裡插入圖片描述

輸入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
輸出:6
解釋:上面是由陣列 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。 

示例2:

輸入:height = [4,2,0,3,2,5]
輸出:9

提示:

  • n = = h e i g h t . l e n g t h n == height.length n==heig
    ht.length
  • 0 ≤ n ≤ 3 ∗ 1 0 4 0 \le n \le 3 * 10^4 0n3104
  • 0 ≤ h e i g h t [ i ] ≤ 105 0 \le height[i] \le 105 0height[i]105

題解:

這題可以從兩個方向考慮,一種是豎著,一種是橫著。

法一(豎著):

只考慮 i 位置上方能放幾格水。那麼很容易想到 i 位置上方水的數量的表示式:max{min{ i 左側的最大值, i 右側的最大值} - height[i], 0}

對所有位置的結果累加求和就是最終結果。然後再求上述表示式的結果時,就有一些值得探究的地方了。

  • 最基礎的解法就是暴力了,對於每個位置 i

    ,用兩個迴圈求 [0...i-1] 的最大值和 [i+1,n-1] 的最大值。但是這題資料肯定過不去,程式碼就不貼了。

  • 其實可以不用那麼暴力,我們可以額外使用兩個陣列 leftMaxrightMaxleftMax[i] 表示 [0...i] 的最大值,right[i] 表示 [i...n-1] 的最大值。從左往右遍歷一遍,生成 leftMax;從右往左一遍,生成 rightMax。程式碼如下:

    class Solution {
    public:
        int trap(vector<int>& height) {
            int n = height.size
    (); if ( n < 3 ) return 0; vector<int> leftMax( n ), rightMax( n ); leftMax[0] = height[0]; for ( int i = 1; i < n; ++i ) leftMax[i] = max( leftMax[i - 1], height[i] ); rightMax[n - 1] = height[n - 1]; for ( int i = n - 2; i >= 0; --i ) rightMax[i] = max( rightMax[i + 1], height[i] ); int ret = 0; for ( int i = 1; i < n - 1; ++i ) ret += max( min( leftMax[i - 1],rightMax[i + 1] ) - height[i], 0 ); return ret; } }; /* 時間:8ms,擊敗:90.86% 記憶體:14.1MB,擊敗:90.60% */
  • 其實還可以使用雙指標,做到額外空間複雜度 O ( 1 ) O(1) O(1) 。具體就是:使用兩個指標 lr ,開始分別指向 0n - 1 ,兩個變數 leftMaxrightMax ,分別表示 l 左邊的最大值和 r 右邊的最大值,開始時為 arr[0]arr[n - 1],每一步讓 l 移動或者 r 移動,具體分情況討論:

    在這裡插入圖片描述

    1. leftMax <= rightMax ,此時可以求出 l 位置上方的水量。因為 rightMaxarr[r + 1...n - 1] 的最大值,而 l 的右側還有一個未知區域,所以 l 右側最大值一定不會小於 rightMaxleftMax 代表 l 左側的最大值,因為 leftMax <= rightMax ,可知 leftMaxl 位置的瓶頸。故 l 位置上方的水量為 max{leftMax - arr[l], 0} ,然後讓 l 向右移動,並且移動前需要更新 leftMax = max{leftMax, arr[l]}
    2. leftMax > rightMax ,分析同 1。

    程式碼如下:

    class Solution {
    public:
        int trap(vector<int>& height) {
            int n = height.size();
            if ( n < 3 ) return 0;
            int lmax = height[0], rmax = height[n - 1];
            int ret = 0, l = 1, r = n - 2;
            while ( l <= r ) {
                if ( lmax <= rmax ) {
                    ret += max( lmax - height[l], 0 );
                    lmax = max( lmax, height[l++] );
                } else {
                    ret += max( rmax - height[r], 0 );
                    rmax = max( rmax, height[r--] );
                }
            }
            return ret;
        }
    };
    /*
    時間:0ms,擊敗:100.00%
    記憶體:13.8MB,擊敗:95.18%
    */
    
法二(橫著):

就是考慮一個空格往左往右最長能延長到什麼地方(按層的意思)。

我們可以觀察到,當柱子之間形成一個 u 形槽時,就會積累雨水。我們維護一個棧底到棧頂嚴格遞減的單調棧,如果 arr[i] >= arr[stk.top()] ,說明此時形成了 凹槽 ,將棧頂元素彈出,記為 top ,此時增加的雨水量是:(i - stk.top() - 1) * (min(arr[i], arr[stk.top()]) - arr[top])

法二程式碼:

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if ( n < 3 ) return 0;
        vector<int> stk;
        int top, ret = 0;
        for ( int i = 0; i < height.size(); ++i ) {
            while ( stk.size() && height[stk.back()] <= height[i] ) {
                top = stk.back();
                stk.pop_back();
                if ( !stk.size() ) break;
                ret += ( i - stk.back() - 1 ) * (min( height[i], height[stk.back()] ) - height[top]);
            }
            stk.push_back( i );
        }
        return ret;
    }
};
/*
時間:8ms,擊敗:90.86%
記憶體:13.9MB,擊敗:94.12%
*/