zoukankan      html  css  js  c++  java
  • Hard | LeetCode 42. 接雨水 | 单调栈 | 双指针

    42. 接雨水

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

    示例 1:

    img
    输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
    输出:6
    解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 
    

    示例 2:

    输入:height = [4,2,0,3,2,5]
    输出:9
    

    提示:

    • n == height.length
    • 0 <= n <= 3 * 104
    • 0 <= height[i] <= 105

    解题思路

    方法一:暴力

    对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。

    时间复杂度: O(N ^ 2) 空间复杂度: O(1)

    public int trap(int[] height) {
        int ans = 0;
        int size = height.length;
        for (int i = 1; i < size - 1; i++) {
            int max_left = 0, max_right = 0;
            // 找左边的最大值
            for (int j = i; j >= 0; j--) {
                max_left = Math.max(max_left, height[j]);
            }
            // 找右边的最大值
            for (int j = i; j < size; j++) { //Search the right part for max bar size
                max_right = Math.max(max_right, height[j]);
            }
            // 当前柱子能接的雨水量就是(左边最大值, 右边最大值)的较小者 减去 当前柱子的高度
            ans += Math.min(max_left, max_right) - height[i];
        }
        return ans;
    }
    

    方法二:更好的暴力

    在方法一的基础上, 利用动态规划优化左边的最大值和右边的最大值的计算方法。

    时间复杂度: O(N ^ 2) 空间复杂度: O(N)

    public int trap(int[] height) {
        if (height == null || height.length == 0)
            return 0;
        int ans = 0;
        int size = height.length;
        // 先使用动态规划的方法, 使用两个辅助数组分别记录左右两边的最大值
        int[] left_max = new int[size];
        int[] right_max = new int[size];
        left_max[0] = height[0];
        for (int i = 1; i < size; i++) {
            left_max[i] = Math.max(height[i], left_max[i - 1]);
        }
        right_max[size - 1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            right_max[i] = Math.max(height[i], right_max[i + 1]);
        }
        // 当前柱子能接的雨水量就是(左边最大值, 右边最大值)的较小者 减去 当前柱子的高度
        for (int i = 1; i < size - 1; i++) {
            ans += Math.min(left_max[i], right_max[i]) - height[i];
        }
        return ans;
    }
    

    方法三: 单调递减栈

    只有在高度下降的时候形成一个低洼, 这样等到高度上升的时候, 形成一个凹槽。然后将栈中比当前元素小的值出栈, 并且这些出栈的储水量是已经可以明确了, 就是这个元素的值。因为是较小者决定了能够储蓄水的高度。

    public int trap(int[] height) {
        int ans = 0, current = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        while (current < height.length) {
            // 单调栈增加元素, 首先将栈顶比当前数字小的元素全部出栈
            while (!stack.isEmpty() && height[current] > height[stack.peek()]) {
                int top = stack.pop();
                if (stack.isEmpty())
                    break;
                // 计算当前的柱子, 到出栈元素 柱子的横坐标之差
                int distance = current - stack.peek() - 1;
                int bounded_height = Math.min(height[current], height[stack.peek()]) - height[top];
                ans += distance * bounded_height;
            }
            stack.push(current++);
        }
        return ans;
    }
    

    方法四: 双指针法

    所以我们可以认为如果一端有更高的条形块(例如右端),积水的高度依赖于当前方向的高度(从左到右)。 当我们发现另一侧(右侧)的条形块高度不是最高的,我们则开始从相反的方向遍历(从右到左)。 在遍历时维护 left_max 和 right_max ,但是我们现在可以使用两个指针交替进行,实现 1 次遍历即可完成。

    双指针移动时, 每次一定都是指针小的那个移动, 因为指针大的移动没有意义了。

    如果指针移到了更大的位置, 宽度减小了, 高度还是不变。移到了更小的位置, 则宽度减小了, 高度也减小了, 得到了一个更小的结果, 没有意义。所以每次都是指针小的指向对方方向移动。

    时间复杂度: O(N) 空间复杂度: O(1)

    public int trap(int[] height) {
        int left = 0, right = height.length - 1;
        int ans = 0;
        int left_max = 0, right_max = 0;
        while (left < right) {
            // left指针值小于right指针值, 说明当前应该处理左指针, 因为此时存水的高度由左指针决定
            if (height[left] < height[right]) {
                if (height[left] >= left_max) {
                    // 左边没有比当前更大的值, 说明此left不是一块洼地, 不能存水
                    left_max = height[left];
                } else {
                    // height[left] < left_max 说明此地方是一块洼地, 可以存水
                    // 并且存水的高度就是洼地的高度
                    ans += (left_max - height[left]);
                }
                ++left;
            } else {
                //  此else分支说明当前存水的高度由右指针决定
                if (height[right] >= right_max) {
                    // 右边没有比当前更大的值,说明当前right位置, 不能存水
                    right_max = height[right];
                } else {
                    ans += (right_max - height[right]);
                }
                --right;
            }
        }
        return ans;
    }
    
  • 相关阅读:
    luogu P1833 樱花 看成混合背包
    luogu P1077 摆花 基础记数dp
    luogu P1095 守望者的逃离 经典dp
    Even Subset Sum Problem CodeForces
    Maximum White Subtree CodeForces
    Sleeping Schedule CodeForces
    Bombs CodeForces
    病毒侵袭持续中 HDU
    病毒侵袭 HDU
    Educational Codeforces Round 35 (Rated for Div. 2)
  • 原文地址:https://www.cnblogs.com/chenrj97/p/14623396.html
Copyright © 2011-2022 走看看