42、接雨水

42、接雨水

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

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

示例 1:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6

示例 2:

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

思路

1、遍历枚举:

  • 分析示例可知一下特点:
    • 当下完雨之后,整个图形是一个土字形。
  • 因此,可以知道前半部分是按照递增处理的,后半部分是按照递减处理的。
  • 所以,找到最大值位置(多个最大值没关系),分两种情况(前半段递增,后半段递减)统计即可。

2、动态规划

  • 两个数组,记录左右最大高度数组
  • 遍历数组,求出面积交际的区域,就是接到的雨水量

3、单调栈

用一个栈,存储遍历的结果,根据结果的大小,判断是继续入栈还是先出栈计算接水量。

  • 当前元素大于栈顶元素,则计算其中区域的节水量,否则继续入栈
  • 计算其中区域的接水量,需要逐个出栈进行计算。

4、双指针

动态规划的优化算法,我们可以:

  • 只维护左右指针,以及左右当前的最大高度
  • 没有相遇时:
    • 更新左右最大值

1、左右坑洼枚举 $O(n^2)$

思路:只有坑洼出可以储水,那么按照以下思路进行计算水量:
- 从左到右,计算坑洼的水量,开始位置i,找到第一比i高的地方j,然后计算i-j内可以接到的雨水量。

class Solution {
    public int trap(int[] height) {
        int sum = 0;
        int len = height.length;
        boolean up = true;
        int h2 = 0,h3;// 记录最大值的下标
        for (int i = 0;i < height.length;i++) {// 上坡
            h2 = i;// 记录开始位置
            for (int j = i+1;j < height.length;j++) {// 顺序找到第一个不比我矮的
                if (height[j] >= height[i]) {
                    h2 = j;
                    break;
                }
            }
            if (h2 > i){// 找到了
                int start = height[i];
                for (int j = i+1;j < h2;j++){
                    sum += start - height[j];// 逐个位置更新水量
                }
                i = h2 - 1;
            }else {// 没有一个比我高呀
                break;// 退出循环,从左到右找到最大值
            }
        }
        if (h2 < len - 1) {// 下坡
            h3 = h2;
            for (int i = len - 1;i >= h3;i--){// 下坡
                h2 = i;// 记录开始位置
                for (int j = i - 1;j >= h3;j--){// 逆序找第一个不比我矮的
                    if (height[j] >= height[i]) {
                        h2 = j; // 找到了
                        break;
                    }
                }
                if (h2 < i) {// 找到了
                    int start = height[i];
                    for (int j = i-1;j > h2;j--){
                        sum += start - height[j];// 逐个位置更新水量
                    }
                    i = h2 + 1;
                }
            }
        }
        return sum;
    }
}

思路优化

  • 我们可以先找到整个数组中的最大值及其下标,避免全量遍历的时间复杂度
  • 左右开弓进行计算接到的雨水量
class Solution {
    public int trap(int[] height) {
        if(height==null || height.length<3)
            return 0;
        int area = 0;
        int leftHeight = height[0];
        int rightHeight = height[height.length-1];
        int maxHeight = height[0];
        int maxIndex = 0;
        for(int i=1; i<height.length; i++){// 找到最大值,以及分割点
            if(height[i] > maxHeight){
                maxHeight = height[i];
                maxIndex = i;
            }
        }
        // 整体递增部分
        for(int i=1; i<maxIndex; i++){
            if(height[i] < leftHeight)
                area += leftHeight-height[i];
            else
                leftHeight = height[i];
        }
        // 整体递减部分
        for(int i=height.length-1; i>maxIndex; i--){
            if(rightHeight > height[i])
                area += rightHeight - height[i];
            else
                rightHeight = height[i];
        }
        return area;
    }
}

2、动态规划 $O(n)$

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

        int[] leftMax = new int[n];
        leftMax[0] = height[0];
        for (int i = 1; i < n; ++i) {
            // leftMax[i] 取当前遇到的最高的height
            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] 取当前遇到的最高的height
            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;
    }
}

3、单调栈 $O(n)$

class Solution {
    public int trap(int[] height) {
        int ans = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        int n = height.length;
        for (int i = 0; i < n; ++i) {
            // height[i] 大于栈顶高度,则逐个出栈,计算可以接到的雨水量
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int top = stack.pop();// 循环逐个屁、pop
                if (stack.isEmpty()) {
                    break;
                }
                // 栈中第一个元素的下标
                int left = stack.peek();
                int currWidth = i - left - 1;
                // 这里 要去取一个最小高度,减去内部的高度值
                int currHeight = Math.min(height[left], height[i]) - height[top];
                ans += currWidth * currHeight;
            }
            stack.push(i);
        }
        return ans;
    }
}

4、双指针 $O(n)$

本质是木桶效应

class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int n = height.length;

        int left = 0;
        int right = n - 1;
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = Math.max(leftMax, height[left]);
            rightMax = Math.max(rightMax, height[right]);
            // case1 find save water in left
            if (height[left] < height[right]) {
                ans += leftMax - height[left];
                ++left;// move left
            } else { // case2 find save water in right
                ans += rightMax - height[right];
                --right;// move right
            }
        }
        return ans;
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1056615746@qq.com

💰

Title:42、接雨水

Count:1.3k

Author:攀登

Created At:2024-06-02, 15:19:44

Updated At:2024-06-15, 15:52:32

Url:http://jiafeimao-gjf.github.io/2024/06/02/42%E3%80%81%E6%8E%A5%E9%9B%A8%E6%B0%B4/

Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.

×

Help us with donation