42.接雨水[困难]

473 阅读1分钟

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

图片.png

解法一:暴力解法

基本思路

要能接到雨水,那么需要两侧的柱子比自己高才行,而且还取决于两个柱子较低的那个。较低的柱子与当前柱子的高度差就是该柱子可以接到的雨水。

也就是问题转换成:求每个柱子左侧的最高柱子和右侧最高的柱子。

注意:有可能自己就是最高的柱子。这样接到的雨水就是0。

代码实现

    public int trap(int[] height) {
        int res = 0;
        for (int i = 0; i < height.length; i++) {
            int leftMax = 0, rightMax = 0;
            for (int j = i; j >= 0; j--) {
                leftMax = Math.max(leftMax, height[j]);
            }
            for (int j = i; j < height.length; j++) {
                rightMax = Math.max(rightMax, height[j]);
            }
            res += Math.min(leftMax, rightMax) - height[i];
        }
        return res;
    }

复杂度

时间复杂度:O(n^2)

空间复杂度:O(1)

解法二

基本思路

上面的想法在寻找左右两侧的最大值时,使用了两层循环,效率比较低。考虑在一层循环中解决。

以寻找左侧的最大值为例:由于遍历是从0号开始的,假设用leftMax[i]表示第i处左侧的最大值,那么leftMax[i-1]已经是第i-1处左侧的最大值了,那么第i处的最大值可以表示为Math.max(leftMax[i-1], height[i])。

在寻找右侧的最大值时想法一样,只不过要倒序遍历。

代码

public int trap(int[] height) {
    if (height.length == 0) {
        return 0;
    }

    int[] leftMax = new int[height.length];
    int[] rightMax = new int[height.length];
    leftMax[0] = height[0];
    for (int i = 1; i < height.length; i++) {
        leftMax[i] = Math.max(leftMax[i-1], height[i]);
    }
    rightMax[height.length-1] = height[height.length-1];
    for (int i = height.length - 2; i >= 0; i--) {
        rightMax[i] = Math.max(rightMax[i+1], height[i]);
    }

    int res = 0;
    for (int i = 0; i < height.length - 1; i++) {
        res += Math.min(leftMax[i], rightMax[i]) - height[i];
    }
    return res;
}

复杂度

时间复杂度:O(n)

空间复杂度:O(n)

解法三

基本思路

上面的解法空间复杂度太高了。在解题过程中,总是选择的是左右两侧较小的。使用左右双指针的方式来解决。

代码

    public int trap(int[] height) {
        if (height.length == 0) {
            return 0;
        }
        
        int res = 0;
        int left = 0, right = height.length - 1;
        int leftMax = height[0], rightMax = height[height.length - 1];
        while (left <= right) {
            // 下面总是选择两侧较小的那个
            if (leftMax <= rightMax) {
                // 左侧的最大值小
                // 计算新的leftMax,left递增
                leftMax = Math.max(leftMax, height[left]);
                res += leftMax - height[left];
                left++;
            } else {
                // 右侧的最大值小
                // 计算新的rightMax,right递减
                rightMax = Math.max(rightMax, height[right]);
                res += rightMax - height[right];
                right--;
            }
        }
        return res;
    }

复杂度

时间复杂度:O(n)

空间复杂度:O(1)

解法四:单调递减栈

思路

这个方法太难了。

栈中的元素是逐渐递减的。只有当元素比栈顶小的时候才入栈(即使当前高度为0,也会入栈)。如果发现元素比栈顶的大,说明可能形成了低洼地带。在算面积时,需要将上一个先出栈,因为只有3个元素才能形成个低洼地带。

代码

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

参考

-接雨水的前三种方法讲的挺好

-官方单调递减栈