【每日刷题】接雨水

42. 接雨水 - 力扣(LeetCode)

方法一:模拟

开始想到【盛最多水的容器】这个题,试了一下发现没有办法借用它的思路。后面想了想,先找到最高的柱子,再分别向左和向右找第二高的柱子,计算中间接的水,然后继续分别向左向右……思路是可行的,时间复杂度也是极高的,左右都是双层循环,跑出了577ms的耗时【望天】不过也是过了,你就说过没过吧。

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        int ans = 0;
        //求出最高的柱子所在位置
        int mx = 0, pos = 0;
        for(int i = 0; i < n; i++) {
            if(height[i] >= mx) {
                mx = height[i];
                pos = i;
            }
        }

        //处理左边的部分
        int mx1 = 0, pos1 = 0;
        int mx2 = mx, pos2 = pos; //pos2更靠近中心
        while(pos2 > 0) {
            mx1 = 0;
            pos1 = 0;
            for(int i = pos2 - 1; i >= 0; i--) {
                if(height[i] > mx1) {
                    mx1 = height[i];
                    pos1 = i;
                }
            }
            ans += (pos2 - pos1) * mx1;
            for(int i = pos1; i < pos2; i++) {
                ans -= height[i];
            }
            mx2 = mx1;
            pos2 = pos1;
        }

        //处理右边的部分
        mx2 = mx;
        pos2 = pos;
        while(pos2 < n - 1) {
            mx1 = 0;
            pos1 = n-1;
            for(int i = pos2 + 1; i < n; i++) {
                if(height[i] > mx1) {
                    mx1 = height[i];
                    pos1 = i;
                }
            }
            ans += (pos1 - pos2) * mx1;
            for(int i = pos2 + 1; i <= pos1; i++) {
                ans -= height[i];
            }
            mx2 = mx1;
            pos2 = pos1;
        }
        return ans;
    }
}

 

方法二:动态规划

开两个数组,leftMax[]和rightMax[],leftMax[i] 表示 0 到 i 的位置 height 的最大高度,rightMax[i] 表示 i 到 n-1 的位置 height 的最大高度。因此,我们只需要正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。两次遍历结束后,下标 i 处能接的雨水量等于 min(leftMax[i], rightMax[i]) - height[i] 。遍历每个下标位置即可得到能接的雨水总量。

Java版本

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        int leftMax[] = new int[n];
        leftMax[0] = height[0];
        for(int i = 1; i < n; i++) {
            leftMax[i] = Math.max(leftMax[i-1], height[i]);
        }
        int rightMax[] = new int[n];
        rightMax[n-1] = height[n-1];
        for(int i = n-2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i+1], height[i]);
        }
        
        int ans = 0;
        for(int i = 0; i < n; i++) {
            ans += Math.min(leftMax[i], rightMax[i]) - height[i];
        }
        return ans;
    }
}

Python版本

class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        leftMax = [height[0]] + [0] * (n - 1)
        for i in range(1, n):
            leftMax[i] = max(leftMax[i-1], height[i])
        rightMax = [0] * (n-1) + [height[n-1]]
        for i in range(n - 2, -1, -1):
            rightMax[i] = max(rightMax[i+1], height[i])

        ans = 0
        for i in range(n):
            ans += min(leftMax[i], rightMax[i]) - height[i]
        return ans
  • 时间复杂度:O(n),其中 n 是数组 height 的长度。计算数组 leftMax 和 rightMax 的元素值各需要遍历数组 height 一次,计算能接的雨水总量还需要遍历一次。

  • 空间复杂度:O(n),其中 n 是数组 height 的长度。需要创建两个长度为 n 的数组 leftMax 和 rightMax。

方法三:双指针

在方法二里,下标 i 处能接的雨水量由 leftMax[i] 和 rightMax[i] 中的最小值决定,我们可以直接使用两个指针 left 和 right 和两个变量 leftMax 和 rightMax 代替两个数组。

我们使用 height[left] 和 height[right] 的值更新 leftMax 和 rightMax 的值。如果 height[left] < height[right] ,则必有 leftMax < rightMax,下标 left 处能接的雨水量等于 leftMax−height[left]。反之如果 height[left] ≥ height[right],则必有 leftMax ≥ rightMax,下标 right 处能接的雨水量等于 rightMax−height[right]。

当两个指针相遇时,遍历结束,得到最后答案。

Java版本

class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0;
        while(left < right) {
            leftMax = Math.max(leftMax, height[left]);
            rightMax = Math.max(rightMax, height[right]);
            if(height[left] < height[right]) {
                ans += leftMax - height[left];
                left++;
            } else {
                ans += rightMax - height[right];
                right--;
            }
        }
        return ans;
    }
}

Python版本

class Solution:
    def trap(self, height: List[int]) -> int:
        ans = 0
        left, right = 0, len(height) - 1
        leftMax = rightMax = 0
        while left < right:
            leftMax = max(leftMax, height[left])
            rightMax = max(rightMax, height[right])
            if(height[left] < height[right]):
                ans += leftMax - height[left]
                left += 1
            else:
                ans += rightMax - height[right]
                right -= 1
        return ans
  • 时间复杂度:O(n),其中 n 是数组 height 的长度。两个指针的移动总次数不超过 n。

  • 空间复杂度:O(1)。只需要使用常数的额外空间。

 

总结

方法一写下来只是记一个思路,方法二动态规划……万物皆可DP(确信),方法三是在方法二的基础上优化了空间,思路没太大变化,但是把空间复杂度由O(n)降到O(1)。

官方题解下还有一个单调栈的方法,用单调栈计算能接的雨水总量。感觉有点难理解并且难想到,自己也是一知半解,就不写在这里了。感兴趣的可以自己去看看。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值