[力扣]42. 接雨水

解法一 动态规划

class Solution:
    def trap(self, height: List[int]) -> int:
        t,n = 0,len(height)
        L,R = [0] * n,[0] * n
        for i in range(n):
            t = max(height[i],t)
            L[i] = t
        t = 0
        for i in range(n - 1,-1,-1):
            t = max(height[i],t)
            R[i] = t
        res = 0
        for i in range(n):
            res += min(R[i],L[i]) - height[i]
        return res

这种操作其实非常简单。

本题问能接到多少雨水,能接到的雨水的总数等于各个位置接到的雨水的总和。

第 i 的 位 置 能 接 到 的 雨 水 = 第 i 个 位 置 的 雨 水 高 度 − h e i g h t [ i ] 第i的位置能接到的雨水=第i个位置的雨水高度 - height[i] i=iheight[i]

i i i个位置能接到多高的雨水呢?
这个有第 i i i个位置的两侧的的地平面高度决定。

我们用 L 、 R L、R LR两个列表分别记录位于第 i i i个位置的两侧的地平面的最高高度为多少,那么

第 i 的 位 置 能 接 到 的 雨 水 = m a x ( L [ i ] , R [ i ] ) 第i的位置能接到的雨水 = max(L[i],R[i]) i=max(L[i],R[i])

解法二 双指针算法

class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        lm,rm = 0,0
        l,r = 0,n - 1
        res = 0
        while l < r:
            lm,rm = max(lm,height[l]),max(rm,height[r])
            if height[l] < height[r]:
                res += lm - height[l]
                l += 1
            else:
                res += rm - height[r]
                r -= 1
        return res

双指针算法的思路实际上是对于动态规划的优化。
它同样计算逐个累加计算了每一个能接到的雨水,最终得出总的雨水量。

相比于动态规划的思路,它在时间和空间上的复杂度都有所优化。
时间复杂度为 O ( n ) O(n) O(n),相比于动态规划优化了线性的时间;
空间复杂度为 O ( 1 ) O(1) O(1),免去了统计每一个位置左右最大高度所需的数组空间。

双指针算法免去了统计每个位置的左右最高高度所需的数组,并不意味着计算每一个位置所接的雨水不需要知道其左右最大值,而是巧妙地运用双指针算法让枚举每一个值时都能从双指针中得出其左右最大值。

首先我们应该清楚一点,当前位置能够盛的雨水量,与左右两侧最高的柱子中较低的那一个有关,而非与其中较高的那一个有联系。

[力扣]42. 接雨水_第1张图片
如上图所示,当我们在右方发现任意一个比lm(左边最高高度)高的位置时,当前位置可接的雨水量便已经确定—— l m − h e i g h t [ i ] lm - height[i] lmheight[i]

因此假如我们从两个分别向中间靠拢进行枚举,即使用两个指针分别从两边向中间位置进行枚举时,当前的左右两边的最高高度(lm、rm)中较小的那一方的指针指向的位置所能接的水的量便已经确定。

如果两边的最高高度相等,即 l m = r m lm = rm lm=rm呢?
那么意味着当前两边位置到没有接到水,可以直接向中间推进一个位置——但由于使用上方的计算结果同样为0,所以无需为相等的情况做额外的区分。

解法三 单调栈

class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        res = 0
        S = []
        for i in range(n):
            if S:
                while S and height[i] > height[S[-1]]:
                    t = S.pop()
                    if not S:break
                    k = S[-1]
                    res += (i - k - 1) * (min(height[k],height[i]) - height[t])
            S.append(i)
        return res

这种算法的思路与上方的两种算法的区别比较大,单调栈算法更像是在模拟了大雨中垒砖——垒柱子是的一个过程。

[力扣]42. 接雨水_第2张图片
如图,我们注意图中的当前位置,由于当前位置的右边为空,因此当前位置所能接到的雨水量为0。
而当我们在其右方放了一 柱子,当前位置的雨水量便变为了1。
如下图所示。
[力扣]42. 接雨水_第3张图片
继续,当我再放一 更高的柱子时,情况又有所变化。
如下图所示。

[力扣]42. 接雨水_第4张图片
我们发现,不仅当前位置的水位变高了,而且周围两个位置的水位也同样变高了。

因此,如果我们知道当前位置的左边最高高度,再向右方枚举柱子高度,在遇到更高的柱子时更新高度,便能最终得出总的雨水量了。

在代码的实现上,我们可以维护一个降序的单调栈,在每一次枚举到一个更高的新位置时,通过如上的模拟步骤计算相应的接水量。


题目链接
原创不易,感谢支持!

你可能感兴趣的:(python,leetcode,题解,leetcode,算法,职场和发展)