42. 接雨水

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

示例 1:

输入: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
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 10

解法1(会因为时间复杂度原因导致失败)

思路,一层一层进行遍历

红色区域中的水,数组是 height = [ 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 ] 。

原则是高度小于 1,temp ++,高度大于等于 1,ans = ans + temp,temp = 0。

temp 初始化为 0,ans = 0

height[0] 等于 0 < 1,不更新。

height[1] 等于 1 >= 1,开始更新 temp。

height[2] 等于 0 < 1,temp = temp + 1 = 1。

height[3] 等于 2 >= 1,ans = ans + temp = 1,temp = 0。

height[4] 等于 1 >= 1,ans = ans + temp = 1,temp = 0。

height[5] 等于 0 < 1,temp = temp + 1 = 1。

height[6] 等于 1 >= 1,ans = ans + temp = 2,temp = 0。

剩下的 height[7] 到最后,高度都大于等于 1,更新 ans = ans + temp = 2,temp = 0。而其实 temp 一直都是 0,所以 ans 没有变化。

public int trap(int[] height) {
    // 如果输入的height为空或者长度为0,直接返回0,没有可以存水的地方
    if (height == null || height.length == 0) return 0;

    // 找到数组中最高的高度,作为外层循环的上限
    int max = Arrays.stream(height).max().getAsInt();
    int len = height.length;
    int ans = 0;

    // 外层循环,从高度1遍历到最高高度max,逐层处理
    for (int i = 1; i <= max; i++) {
        // 标记是否开始遇到低于当前层高度i的有效区域,初始化为false
        boolean isStart = false;
        int tmp = 0; // 临时记录当前层i下,两个有效边界之间的低于i的高度的格子数

        // 内层循环,遍历整个height数组
        for (int j = 0; j < len; j++) {
            // 如果当前位置的高度大于等于当前层高度i,说明遇到了一个有效边界
            if (height[j] >= i) {
                // 将临时记录的tmp加到结果ans中,因为当前遇到了新的边界,中间的tmp就是这两个边界之间的存水量
                ans = ans + tmp;
                tmp = 0; // 重置临时变量
                isStart = true; // 标记已经开始遇到有效边界
            }
            // 如果当前位置的高度小于当前层高度i,并且已经开始遇到有效边界(isStart为true),说明处于两个有效边界之间的区域
            if (height[j] < i && isStart) {
                tmp++; // 临时记录这个低于i的格子数,用于后续遇到下一个边界时计算存水量
            }
        }
    }

    return ans;
}

注意:

1.当你把tmp的初始化,置于方法外面的时候,会导致一些错误出现

2.为什么要设置isStart呢?

为了判断第一次遇见左边界的时候,才会真正增加雨水格子。

3.为什么没有右边界的判断呢?

因为判断height[j] >= i的时候,就相当于判断两次边界了,比如当我是[ 0 , 1 , 0 , 1 , 0 ]的时候

最后一次1,就会因为height [j] >= i 而判断成边界,并且把tmp进行加入,同时置为0。

解法二(动态规划)

public int trap(int[] height) {
    // 获取数组的长度
    int len = height.length;
    // 用于存储最终的雨水总量
    int ans = 0;
    // 左边数组,leftArr[i]表示从数组起始位置到i位置的最高高度
    int[] leftArr = new int[len];
    // 右边数组,rightArr[i]表示从数组末尾位置到i位置的最高高度
    int[] rightArr = new int[len];
    
    // 初始化左边数组的第一个元素,因为它左边没有元素,所以自身就是最高高度
        leftArr[0] = height[0];
    // 从左到右遍历,填充左边数组
    for (int i = 1; i < len; i++) {
        // 当前位置的左边最高高度是左边数组前一个位置的最高高度和当前位置高度的较大值
        leftArr[i] = Math.max(leftArr[i - 1], height[i]);
    }
    
    // 初始化右边数组的最后一个元素,因为它右边没有元素,所以自身就是最高高度
    rightArr[len - 1] = height[len - 1];
    // 从右到左遍历,填充右边数组
    for (int j = len - 2; j >= 0; j--) {
        // 当前位置的右边最高高度是右边数组后一个位置的最高高度和当前位置高度的较大值
        rightArr[j] = Math.max(rightArr[j + 1], height[j]);
    }
    
    // 遍历整个数组,计算每个位置的存水量并累加
    for (int i = 0; i < len; i++) {
        // 当前位置的存水量等于左边最高和右边最高的较小值减去当前位置的高度
        ans += Math.min(leftArr[i], rightArr[i]) - height[i];
    }
    // 返回总的存水量
    return ans;
}



说明:
1. 首先创建了两个辅助数组leftArr和rightArr,分别用来存储每个位置左边的最高高度和右边的最高高度。
2. 遍历填充leftArr数组:从左到右,每个位置i的左边最高高度是前一个位置i-1的左边最高高度和当前位置高度的较大值。
3. 遍历填充rightArr数组:从右到左,每个位置j的右边最高高度是后一个位置j+1的右边最高高度和当前位置高度的较大值。
4. 最后遍历原数组,对于每个位置i,其能存储的雨水量为左边最高高度和右边最高高度的较小值减去当前位置的高度,将所有位置的存水量累加起来就是最终的结果。这种方法利用了预处理的左右最高高度数组,将时间复杂度优化到了O(n),空间复杂度也是O(n)。

图片作者:力扣官方题解
链接:https://leetcode.cn/problems/trapping-rain-water/solutions/692342/jie-yu-shui-by-leetcode-solution-tuvc/
来源:力扣(LeetCode)

### 关于雨水问题的 Java 实现 雨水问题是经典的算法题目之一,其核心在于通过某种方式计算柱子之间的凹槽部分能够存储的水量。以下是基于 **单调栈** 和 **双指针法** 的两种常见解决方案。 --- #### 方法一:单调栈实现 单调栈是一种有效的数据结构用于处理此类区间极值问题。具体逻辑如下: 1. 使用 `Stack<Integer>` 存储柱子索引。 2. 遍历数组中的每一个柱子高度,当遇到当前柱子高于栈顶柱子时,则说明形成了一个可以积水的区域。 3. 计算该区域内的积水量并累加到总结果中。 下面是完整的代码实现: ```java import java.util.Stack; class Solution { public int trap(int[] height) { Stack<Integer> stack = new Stack<>(); int res = 0; for (int i = 0; i < height.length; i++) { while (!stack.isEmpty() && height[i] > height[stack.peek()]) { int top = stack.pop(); // 当前要计算面积的位置 if (stack.isEmpty()) break; int distance = i - stack.peek() - 1; // 左右边界距离 int boundedHeight = Math.min(height[i], height[stack.peek()]) - height[top]; res += distance * boundedHeight; } stack.push(i); } return res; } } ``` 这种方法的时间复杂度为 O(n),空间复杂度也为 O(n)[^1]。 --- #### 方法二:双指针优化 双指针法利用两个变量分别记录左侧最大值和右侧最大值,在遍历时逐步更新这些值,并根据当前位置的高度差来决定是否增加水体积。 以下是具体的代码实现: ```java class Solution { public int trap(int[] height) { int left = 0, right = height.length - 1; int lMax = 0, rMax = 0; int water = 0; while (left < right) { if (height[left] < height[right]) { if (height[left] >= lMax) { lMax = height[left]; } else { water += lMax - height[left]; } left++; } else { if (height[right] >= rMax) { rMax = height[right]; } else { water += rMax - height[right]; } right--; } } return water; } } ``` 这种解法时间复杂度同样为 O(n),但仅需常量级额外空间 O(1)[^2]。 --- #### 提供的代码分析 对于您给出的代码片段[^3],存在一些潜在改进之处: - 函数 `maxRight` 被多次调用,每次都会重新扫描右边的最大值,增加了不必要的开销。 - 可以考虑采用上述提到的方法进一步提升效率。 --- ### 总结 无论是使用单调栈还是双指针方法都可以高效解决问题。前者更直观易懂;后者则更加节省内存资源。实际应用可根据需求选择合适的方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值