LeetCode 42.接雨水

LeetCode 42: 接雨水

题目描述:

给定一个包含非负整数的数组 height,每个元素表示一个柱子的高度。每个柱子宽度为 1,计算按此排列的柱子,下雨之后能接多少雨水。


题目分析

接雨水的关键在于:

  1. 每一个位置能接多少雨水?
    一个柱子位置 i 处能接的雨水量由左右两边的最大高度决定,可以计算为:

    水量(i) = max(0, min(左侧最大高度, 右侧最大高度) - height[i])。
    
    • 如果左侧或右侧的最大高度小于当前柱子高度,那么当前位置无法存水。
    • 关键是如何高效地计算每个位置左右两侧的最大高度。

解法 1:暴力解法

思路:

  1. 遍历数组,对于位置 i,分别计算其左侧和右侧的最大高度:

    • leftMax:从 0i 的最大值。
    • rightMax:从 in-1 的最大值。
  2. 根据公式计算雨水量:

    java 复制代码
    rain[i] = Math.max(0, Math.min(leftMax, rightMax) - height[i]);

模板代码:

java 复制代码
class Solution {
    public int trap(int[] height) {
        int n = height.length;
        int totalWater = 0;

        for (int i = 0; i < n; i++) {
            int leftMax = 0, rightMax = 0;

            // 找到左边的最大高度
            for (int j = 0; j <= i; j++) {
                leftMax = Math.max(leftMax, height[j]);
            }

            // 找到右边的最大高度
            for (int j = i; j < n; j++) {
                rightMax = Math.max(rightMax, height[j]);
            }

            // 计算当前位置能接的雨水
            totalWater += Math.max(0, Math.min(leftMax, rightMax) - height[i]);
        }

        return totalWater;
    }
}

时间和空间复杂度:

  • 时间复杂度:O(N²)
    两层嵌套循环分别计算左右最大高度。
  • 空间复杂度:O(1)
    没有额外使用数组。

解法 2:预处理 + 动态规划

思路:

  1. 使用两个数组 leftMaxrightMax 分别存储当前位置左侧和右侧的最大高度,使用 动态规划 来提前计算:

    • leftMax[i]:表示从 0i 的最大高度:

      leftMax[i] = Math.max(leftMax[i-1], height[i])。
      
    • rightMax[i]:表示从 in-1 的最大高度:

      rightMax[i] = Math.max(rightMax[i+1], height[i])。
      
  2. 遍历一次数组,按公式计算雨水量。

模板代码:

java 复制代码
class Solution {
    public int trap(int[] height) {
        int n = height.length;
        if (n == 0) return 0;

        // 预处理左侧和右侧的最大高度
        int[] leftMax = new int[n];
        int[] rightMax = new int[n];

        // 计算左侧最大
        leftMax[0] = height[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], height[i]);
        }

        // 计算右侧最大
        rightMax[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i + 1], height[i]);
        }

        // 计算雨水总量
        int totalWater = 0;
        for (int i = 0; i < n; i++) {
            totalWater += Math.max(0, Math.min(leftMax[i], rightMax[i]) - height[i]);
        }

        return totalWater;
    }
}

时间和空间复杂度:

  • 时间复杂度:O(N)
    预处理中遍历两次数组,计算leftMaxrightMax,然后最后遍历一次数组。
  • 空间复杂度:O(N)
    需要额外的 leftMaxrightMax 数组。

解法 3:双指针

思路:

  • 使用两个指针 leftright 分别指向数组的两端。
  • 维护两个变量 leftMaxrightMax
    • leftMax:从左向右扫描时左侧的最大高度。
    • rightMax:从右向左扫描时右侧的最大高度。
  • 每次比较 leftMaxrightMax
    • 如果 leftMax < rightMax,则左指针位置的水量取决于 leftMax
    • 否则右指针位置的水量取决于 rightMax

模板代码:

java 复制代码
class Solution {
    public int trap(int[] height) {
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0;
        int totalWater = 0;

        while (left <= right) {
            if (leftMax < rightMax) {
                if (height[left] < leftMax) {
                    totalWater += leftMax - height[left];
                } else {
                    leftMax = height[left];
                }
                left++;
            } else {
                if (height[right] < rightMax) {
                    totalWater += rightMax - height[right];
                } else {
                    rightMax = height[right];
                }
                right--;
            }
        }

        return totalWater;
    }
}

时间和空间复杂度:

  • 时间复杂度:O(N)
    双指针只需要遍历一次数组。
  • 空间复杂度:O(1)
    没有使用额外的存储空间。

解法 4:单调栈

思路:

  • 使用栈来存储柱子的索引。
  • 在遍历数组时,维护一个递减栈:
    • 如果当前柱子高度小于栈顶柱子,直接入栈。
    • 如果当前柱子高度大于栈顶柱子,则「尝试闭合洼地」:
      • 栈顶弹出,视为洼地底部。

      • 如果栈不为空,计算当前闭合的水量:

        高度 = min(左边高度, 右边高度) - 洼地底部高度。
        宽度 = 当前索引 - 栈顶索引 - 1。
        
      • 累积水量。

模板代码:

java 复制代码
class Solution {
    public int trap(int[] height) {
        Stack<Integer> stack = new Stack<>();
        int totalWater = 0;

        for (int i = 0; i < height.length; i++) {
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int bottom = stack.pop(); // 弹出洼地底部
                
                if (stack.isEmpty()) break; // 如果栈为空,无法形成洼地

                int left = stack.peek(); // 左边柱子的索引
                int width = i - left - 1; // 宽度
                int waterHeight = Math.min(height[i], height[left]) - height[bottom]; // 高度
                totalWater += width * waterHeight;
            }
            stack.push(i);
        }

        return totalWater;
    }
}

时间和空间复杂度:

  • 时间复杂度:O(N)
    每个柱子仅入栈/出栈一次。
  • 空间复杂度:O(N)
    栈的空间取决于柱子的数量。

经典变体题目

  1. 11. 盛最多水的容器

    • 问题:寻找两个柱子之间的最大盛水面积。
    • 解法:双指针法(面积 = min(height[left], height[right]) × 宽度)。
  2. 407. 接雨水 II(3D 版本)

    • 问题:给定一个二维矩阵,表示高度图,计算可以接的最大雨水。
    • 解法:使用最小堆 + BFS 模拟水位。
  3. 接雨水的连续子区间

    • 问题:问「特定区间」的接水量。
    • 解法:动态规划或双指针进一步改造。
  4. 接雨水路径优化

    • 限制接雨水的路径是否能达到最低点,同时积水量递推。

如何快速 AC?

  • 优先掌握 双指针法(时间 O(N),空间 O(1)),适合面试快速实现。
  • 在需要更复杂分析时,掌握 动态规划单调栈
  • 对变体题目,坚持递归 + 贪心 + 栈的方法融合应对。
相关推荐
未来并未来5 分钟前
深入解析MyBatis中一对一与一对多映射的实现方法与区别
java·数据库·mybatis
ken_coding7 分钟前
使用mybatis plus的@Select自定义sql时,如何实现通用的分页查询?
数据库·spring boot·sql·mybatis plus
微微%7 分钟前
mongodb安装教程以及mongodb的使用
数据库·mongodb
凉、介24 分钟前
ARM 架构下 cache 一致性问题整理
linux·汇编·arm开发·学习·缓存·架构
obboda37 分钟前
web高可用集群项目(数据库主从同步、文件共享存储、nginx动静分离+负载均衡+高可用)
数据库·nginx·负载均衡
m0_7482463540 分钟前
MySQL Workbench安装教程以及菜单汉化
android·数据库·mysql
编程修仙1 小时前
mysql的管理
数据库·mysql·oracle
CodeJourney.1 小时前
Deepseek助力思维导图与流程图制作:高效出图新选择
数据库·人工智能·算法
2的n次方_1 小时前
Redis 中如何保证缓存与数据库的数据一致性
数据库·redis·缓存
m0_748247551 小时前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库