当前位置: 首页 > article >正文

LeetCode 42.接雨水

LeetCode 42: 接雨水

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


题目分析

接雨水的关键在于:

  1. 每一个位置能接多少雨水?
    一个柱子位置 i 处能接的雨水量由左右两边的最大高度决定,可以计算为:
    水量(i) = max(0, min(左侧最大高度, 右侧最大高度) - height[i])。
    
    • 如果左侧或右侧的最大高度小于当前柱子高度,那么当前位置无法存水。
    • 关键是如何高效地计算每个位置左右两侧的最大高度。

解法 1:暴力解法

思路:

  1. 遍历数组,对于位置 i,分别计算其左侧和右侧的最大高度:
    • leftMax:从 0i 的最大值。
    • rightMax:从 in-1 的最大值。
  2. 根据公式计算雨水量:
    rain[i] = Math.max(0, Math.min(leftMax, rightMax) - height[i]);
    

模板代码:

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. 遍历一次数组,按公式计算雨水量。

模板代码:

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

模板代码:

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。
        
      • 累积水量。

模板代码:

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)),适合面试快速实现。
  • 在需要更复杂分析时,掌握 动态规划单调栈
  • 对变体题目,坚持递归 + 贪心 + 栈的方法融合应对。

http://www.kler.cn/a/571895.html

相关文章:

  • 虚拟机IP的配置,让它上网
  • 奖学金(acwing)c++
  • Redis 排行榜实现:处理同分数时的排名问题
  • 探秘基带算法:从原理到5G时代的通信变革【八】QAM 调制 / 解调
  • SSH远程登录并执行命令
  • 【Office-Word】如何自动生成中英文目录
  • 【ATXServer2】Android无法正确显示手机屏幕
  • Android如何将原生的anr,tombstones,dropbox 换分区存储位置
  • 健康养生,开启活力生活
  • Linux驱动开发-字符设备驱动开发
  • 微服务架构中处理用户认证信息的5种方案
  • 数据显示不符合用户阅读习惯
  • Virtual Box虚拟机安装Mac苹果Monterey和big sur版本实践
  • 解决跨域请求的问题(CORS)
  • # 【Unity】【游戏开发】赛车游戏中碰撞加速的实现方法
  • Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器
  • Google Earth Engine中的Map对象
  • 数据结构:二叉树的链式结构及相关算法详解
  • 【TCP/IP协议栈】4. 传输层协议(TCP、UDP)
  • 【PHP】fastadmin中对addons进行路由重写