【完虐演算法】LeetCode 接雨水問題,全覆盤

技術gogogo發表於2021-11-11

大家好!

動態規劃題目是總結的比較完整了。下面是自從和大家刷開題總結的動態規劃解題方法。

今年全國夏天雨是真的多,突然想到今年北京的夏天也不像往年那麼熱。不知不覺就穩穩地度過了夏天來到秋天。

恰巧前幾天有一個粉絲問到了我,網上接雨水的解決總是感覺有點混亂,能不能用動態規劃解決。

今早北京大雨,借用大雨的感受,想了想接雨水問題,依然用長圖一步一步說明!

背景

先來看看題目,這個題目應該是很多人都已經遇到過了,因為它的題號是42,屬於一個比較非常靠前的題目。

同時也屬於一個非常經典的演算法問題。

我們們今天的題目解決不做暴力法、也不做雙指標,就用動態規劃很清晰的進行說明。

看下圖,12根柱子的圍欄,接了6個單位的雨水。

從上圖很顯然能看到一點:

如果想要接住雨水。那麼,決定雨量的多少在於「左邊的柱子高度」、「右邊的柱子高度」以及「自身柱子的高度」。

比如說,中間第 5 格雨水量為2,就是決定於左右側柱子的較小值-本身柱子高度(0)而得到的。

注意:左右側的高度,指的是能圍住雨水的柱子,而不是緊挨著的左右側的柱子。

左側最高柱子:2

右側最高柱子:3

自身柱子高度:0

雨水量 = min(左側最高柱子, 右側最高柱子) - 自身柱子高度 = 2

因此,需要定義兩個陣列,分別來存放相對於當前位置左側和右側柱子的最大高度。

最後,取左右側柱子最小值-自身柱子高度=雨水量。

思路

就用leetcode官方給的案例來進行一步一步解決。

柱子高度為:height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1],再把圖拿過來!

現在,定義兩個陣列,分別命名為 left_max_height right_max_height,存放相對於當前位置左側和右側柱子的最大高度。

先來定義第一個陣列left_max_height,存放左側最高柱子高度:

初始化:(因為左側沒有柱子,所以位置 0 左側最大高度為 0)

left_max_height[0] = 0

動態方程:

left_max_height[i]=max(height[i-1], left_max_height[i-1])

下面還是用長圖一步一步來進行說明:【點選高清顯示】

下面開始定義第二個陣列,存放右側最高柱子高度。右側最高度從最右側開始進行計算。

初始化:(因為最後一個位置由側沒有柱子,所以位置 11 右側最大高度為 0)

right_max_height[11] = 0

動態方程:

right_max_height[j]=max(height[j+1], right_max_height[j+1])

還是用長圖一步一步來進行說明:【點選高清顯示】

現在,當前位置的左側和右側柱子的最大高度陣列計算完成後,下面就計算接水量。

雨水量 = min(左側最高柱子, 右側最高柱子) - 自身柱子高度 = 2

準備好三個陣列:

上圖中,height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1],為了看起來清晰,可以參考最開始圖示。

最大單位水量=左右取最小值-本身的高度

計算方式:

water=min(left_max_height[i], right_max_height[i])-height[i])
max_water=water>=0?water:0

上述需要注意一點,如果左右取最小值-本身的高度<0,說明當前本身柱子是凸出來左右側最高值的,比如說位置 1:

所以說,位置 1 的計算結果為負數,需要強制轉為 0。

程式碼

class Solution(object):
    def trap(self, height):
        size = len(height)
        # 小於等於 2 的時候,是接不住雨水的
        if size <= 2:
            return 0
        # 左邊相對於當前位置的最大高度
        left_max_height = [0 for _ in range(size)]
        # 右邊相對於當前位置的最大高度
        right_max_height = [0 for _ in range(size)]
        # 當前位置接雨水最大高度
        max_water = [0 for _ in range(size)]

        # 初始化 left_max_height, 第 0 個位置初始化為 0
        for i in range(1, size):
            left_max_height[i] = max(height[i-1], left_max_height[i-1])

        # 初始化 right_max_height, 第 size-1 個位置初始化為 0
        for j in range(1, size):
            right_max_height[size-j-1] = max(height[size-j], right_max_height[size-j])

        # 最大水量
        for k in range(1, size):
            max_water[k] = (min(left_max_height[k], right_max_height[k])-height[k] if min(left_max_height[k], right_max_height[k])-height[k]>=0 else 0)

        # 累計求單位水量
        waters = 0
        for z in range(1, size):
            waters += max_water[z]
        return waters

if __name__ == '__main__':
    s = Solution()
    print(s.trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]))

這個題目總體來說,使用動態規劃還是不容易想到的。尤其是上面兩個陣列使用了兩次動態規劃的過程。

接雨水問題還可以使用暴力解法和雙指標解決,雙指標可以試試,至於暴力。。心裡有就好了哈哈。。

多餘的一句

邊工作邊帶大家刷題確實是有點慢了,很抱歉!

不過,一定會堅持下去,也一定會把 LeetCode 各型別題目進行詳細總結,做到大家想檢視的時候隨時能查到詳細 LeetCode 資料的地方。

另外,方便的話也在我的github? 加顆星,它是我持續輸出最大最大的動力,感謝大家!

github:https://github.com/xiaozhutec/share_leetcode

相關文章