LeetCode 42接雨水

249 阅读1分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」。

题目:给定一个数组,数组中元素的大小代表宽为1的柱子高度,求由这些柱子堆成的形状能接多少雨水。

解题思路

直观的想法类似水桶原理,水桶能装的水取决于水桶边低的一面,本题可以看成一个大水桶中多个水桶的累加,每个数字可以看作是一个水桶,对于每个水桶,我们找到水桶的左边界和右边界,之后水桶中的水取决于边界较小的一方,如果当前水桶的数值大于等于左边界,则表示当前水桶装不了水,如果小于左边界且属于左右两个边界的较小值,则当前水桶可装水为边界值减去当前水桶高,若右边界较小,则同理。需要注意的是:此处计算左右边界时需要考虑自身,这样即使边界不满足也不会出现负数(只会出现0的情况)。并且数组的第一个数字和最后一个数字必定是不考虑的,此时这两个数为一个大水桶的两个边界。代码如下:

public static int trap(int[] height) {
        int maxWater = 0;
        int len = height.length;
        for(int i=1;i<len-1;i++){
            int maxLeft=0, maxRight=0;
            for(int m=i;m>=0;m--){
                maxLeft = Math.max(maxLeft, height[m]);
            }
            for(int n=i;n<len;n++){
                maxRight = Math.max(maxRight, height[n]);
            }
            maxWater += Math.min(maxLeft, maxRight) - height[i];
        }
        return maxWater;
    }

上述代码有两层循环,因此时间复杂度为O(N2)O(N^2),仅使用常数量的参数,空间复杂度为O(1)O(1)

动态规划优化

上述方法中对每次遍历的元素都重新循环寻找左边界和右边界,导致时间复杂度变大,实际上,我们可以事先计算好每个元素的左边界和右边界,此处计算的方法和题解中分享的类似,但存在一点不同,题解中都是不考虑当前元素本身,而我的方法是考虑本身,这样在后面就不需要进行值大小的判断,代码如下:

public int trap(int[] height) {
        int maxWater = 0;
        int len = height.length;
        int[] maxLeft = new int[len];
        int[] maxRight = new int[len];
        for(int i=0;i<len;i++){
            if(i>0) {
                maxLeft[i] = Math.max(maxLeft[i - 1], height[i]);
            }else {
                maxLeft[i] = height[i];
            }
        }
        for(int j=len-1;j>=0;j--){
            if(j<len-1) {
                maxRight[j] = Math.max(maxRight[j + 1], height[j]);
            }else {
                maxRight[j] = height[j];
            }
        }
        for(int m=1;m<len-1;m++){
            maxWater += Math.min(maxLeft[m], maxRight[m]) - height[m];
        }
        return maxWater;
    }

上述代码的时间复杂度为O(N)O(N), 空间复杂度为O(N)O(N)

单调递减栈解

另一种思路是利用单调递减栈,接水的条件是保证中间小于两边,可以使用一个栈,保证入栈的元素始终小于栈顶元素,如果大于则当前栈顶元素就是一个可以装水的洼,此时栈顶出栈,之后检查栈是否为空(如果只存在右边界不存在左边界也接不了水),不为空则看当前元素的左边界和右边界哪个小,然后乘水洼的长度即可,该方法有点类似水平计算接水空间(题解对应解法一,按行求)。代码如下:

public int trap(int[] height) {
        int maxWater = 0;
        Stack<Integer> stack = new Stack<>();
        for(int i=0;i<height.length;i++){
            while(!stack.isEmpty()&&height[i]>height[stack.peek()]){
                int cur = stack.pop();
                if(stack.isEmpty()){
                    break;
                }
                int left = stack.peek();
                int min = Math.min(height[i], height[left]) - height[cur];
                maxWater += min*(i-left-1);
            }
            stack.push(i);
        }
        return maxWater;
    }

该方法时间复杂度为O(N)O(N),空间复杂度为O(N)O(N)