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

算法题打卡力扣第42题接雨水(hard)

题目描述

在这里插入图片描述
提示:

n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105

解题思路

一、暴力解

想法:

对每一根柱子,分别找出它左边最高的柱子和右边最高的柱子。
一个位置 i 能接的雨水量,等于它左右两边最高柱子中的较小者的高度,
减去当前柱子height【i】的高度。

具体步骤:

  • 遍历数组中的每一个位置 i(除了首尾,因为它们无法储水)。

  • 对于每个位置 i,再次向左遍历,找到 [0…i-1] 范围内的最高柱子 left_max。

  • 再次向右遍历,找到 [i+1…n-1] 范围内的最高柱子 right_max。 right_max.

  • 在位置 i 能接的雨水量就是 min(left_max, right_max) - height[i]。当然,如果这个值小于0(即当前柱子比两边的矮墙还高),则说明此处无法接水,记为0。

  • 将每个位置计算出的雨水量累加,得到最终结果。

代码实现:

class Solution {
public:int trap(vector<int>& height) {int n=height.size();if(n<=2)return 0;int i=0,j=0,ruin=0,ruinsum = 0;for(i=1;i<=n-1;i++){int leftmax = 0,rightmax = 0;for(j=0;j<=i-1;j++){if(height[j]>leftmax)leftmax=height[j];}for(j=n-1;j>=i+1;j--){if(height[j]>rightmax)rightmax=height[j];}ruin = min(leftmax,rightmax)-height[i];if(ruin<=0)ruin=0;ruinsum = ruinsum+ruin;}return ruinsum;}
};

结果
在这里插入图片描述
超出时间限制了,现在来分析一下复杂度:
有两个嵌套的for循环,时间复杂度应该是O(n^2),空间复杂度是O(1),没有使用额外的空间。

二、动态规划

暴力解法中存在大量的重复计算。例如,在计算位置 i 的左侧最高点时,我们遍历了 0 到 i-1;在计算 i+1
的左侧最高点时,我们又会遍历一遍 0 到 i。我们可以通过预计算并存储结果来优化这个过程。

具体步骤:

  • 创建两个数组,left_max 和 right_max,长度与 height 相同。
  • left_max[i] 用来存储从height[0] 到 height[i] 的最高柱子高度。我们可以从左到右遍历一次 height 数组来填充它:left_max[i] = max(left_max[i-1], height[i])。
  • right_max[i] 用来存储从 height[i] 到 height[n-1] 的最高柱子高度。我们可以从右到左遍历一次 height 数组来填充它:right_max[i] = max(right_max[i+1], height[i])。
  • 再次遍历 height 数组,对于每个位置 i,其能接的雨水量就是 min(left_max[i], right_max[i]) - height[i]。
  • 将所有位置的雨水量累加。

代码:

class Solution {
public:int trap(vector<int>& height) {int n=height.size();if(n<=2)return 0;int i=0,j=0,ruin=0,ruinsum = 0;vector<int> leftmax(n);leftmax[0] = height[0];for(i=1;i<n;i++)leftmax[i] = max(leftmax[i-1],height[i]);vector<int> rightmax(n);rightmax[n-1] = height[n-1];for(i=n-2;i>=0;i--)rightmax[i] = max(rightmax[i+1],height[i]);for(i=1;i<n-1;i++){ruin = min(leftmax[i],rightmax[i])-height[i];if(ruin>0)ruinsum = ruinsum+ruin;}return ruinsum;}
};

在这里插入图片描述
分析一下复杂度
时间复杂度:O(n)
空间复杂度:O(n)

三、双指针

这是对动态规划解法在空间复杂度上的优化。我们不再需要额外的数组来存储左右两边的最大值,而是使用两个指针 left 和 right 从数组的两端向中间移动。

核心思想: 对于任意位置,能接多少水取决于其左右两侧最高柱子中的较矮者。 如果我们从两边向中间处理,当 left_max <right_max 时,对于左指针 left 所在的位置,我们可以确定它能接的雨水量。因为右边的 right_max已经足够高,所以限制左边能接多少水的瓶颈就在于 left_max。反之亦然。

具体步骤:

  • 初始化左指针 left = 0,右指针 right = n-1。
  • 初始化 left_max = 0 和 right_max = 0,用来记录各自方向上遇到的最高柱子。
  • 当 left < right 时,进行循环:
    比较 height[left] 和 height[right]。
    如果 height[left] < height[right]:
    更新 left_max = max(left_max, height[left])。
    更新 left_max = max(left_max,height[left]).

计算当前位置可接的雨水:ans += left_max - height[left]。
将左指针右移:left++。
否则 (height[left] >= height[right]):

更新 right_max = max(right_max, height[right])。

计算当前位置可接的雨水:ans += right_max - height[right]。
将右指针左移:right–。

  • 循环结束后,ans 即为总接雨水量

代码实现:

class Solution {
public:int trap(vector<int>& height) {int n = height.size();if (n <= 2) {return 0;}int left = 0, right = n - 1;int left_max = 0, right_max = 0;int ans = 0;while (left < right) {// 如果左指针处的柱子较矮if (height[left] < height[right]) {// 检查当前柱子是否能成为新的左侧最高点if (height[left] >= left_max) {left_max = height[left];} else {// 如果不能,则说明可以存水ans += (left_max - height[left]);}// 移动左指针left++;} // 如果右指针处的柱子较矮或相等else {// 检查当前柱子是否能成为新的右侧最高点if (height[right] >= right_max) {right_max = height[right];} else {// 如果不能,则说明可以存水ans += (right_max - height[right]);}// 移动右指针right--;}}return ans;}
};

在这里插入图片描述

复杂度分析:
时间复杂度:O(n)
空间复杂度:O(1)

四、单调栈

单调栈通常用于解决“寻找任一元素左/右边第一个比自己大/小的元素”这类问题。对于接雨水问题,我们可以通过维护一个单调递减的栈来解决。

具体步骤:

  • 我们维护一个栈,里面存放柱子的索引,其对应的高度是单调递减的。
  • 遍历 height 数组:
    当栈不为空且当前柱子 height[i] 高度 大于 栈顶索引对应的柱子 height[stack.top()] 时,说明形成了一个可以存水的“凹槽”。
    此时,我们将栈顶元素弹出,它就是凹槽的底部。
    新的栈顶元素就是凹槽的“左墙”。而当前元素 height[i] 就是凹槽的“右墙”。
    凹槽的高度由左右墙中的较矮者决定,即 min(height[new_stack.top()], height[i])。
    凹槽的宽度是 i - new_stack.top() - 1。
    计算出这部分雨水并累加。重复此过程,直到当前柱子不再高于栈顶柱子。
    最后,将当前柱子的索引压入栈中,维持栈的单调性。
    代码
class Solution {
public:int trap(vector<int>& height) {int n = height.size();if (n <= 2) {return 0;}stack<int> st; // 存储索引的单调递减栈int ans = 0;for (int i = 0; i < n; i++) {// 当栈不为空,且当前柱子高于栈顶柱子时,形成凹槽while (!st.empty() && height[i] > height[st.top()]) {// 栈顶是凹槽的底部int mid_idx = st.top();st.pop();// 如果弹出后栈为空,说明没有左边界,无法存水if (st.empty()) {break;}// 新的栈顶是左边界int left_idx = st.top();// 当前的 i 是右边界// 计算宽度int distance = i - left_idx - 1;// 计算高度(由左右边界的较矮者决定)int bounded_height = min(height[i], height[left_idx]) - height[mid_idx];// 累加雨水ans += distance * bounded_height;}// 将当前索引压入栈中st.push(i);}return ans;}
};

在这里插入图片描述

复杂度分析:
时间复杂度:O(n)
空间复杂度:O(1)

依然要感谢g老师~see you next time!

http://www.xdnf.cn/news/17921.html

相关文章:

  • OpenJDK 17的C1和C2编译器实现中,方法返回前插入安全点(Safepoint Poll)的机制
  • 拒绝造轮子(C#篇)ZLG CAN卡驱动封装应用
  • 贺雨禾《梨花往事》北京首映,“野草型演员”深耕走出新赛道
  • 第4问 常见的指标有哪些?
  • 【CVPR2025】计算机视觉|GIFNet:一个模型实现所有图像融合任务!还能增强画质?!
  • [1Prompt1Story] 滑动窗口机制 | 图像生成管线 | VAE变分自编码器 | UNet去噪神经网络
  • 【Qt开发】常用控件(四)
  • 《深度解构:构建浏览器端Redis控制台的WebSocket协议核心技术》
  • 开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
  • Flink Stream API 源码走读 - window 和 sum
  • 前端开发入门书籍推荐:Vue.js 3与前端基础的完美组合
  • 九尾狐未来机械锂晶核
  • 数据结构:满二叉树 (Full Binary Tree) 和 完全二叉树 (Complete Binary Tree)
  • Three.js三大组件:场景(Scene)、相机(Camera)、渲染器(Renderer)
  • tree组件(几种不同分叉树Vue3)
  • 免费万能电子书格式转换器!Neat Converter支持 ePub、Azw3、Mobi、Doc、PDF、TXT 文件的相互转换。
  • 【图像算法 - 15】智能行李识别新高度:基于YOLO12实例分割与OpenCV的精准检测(附完整代码)
  • React手撕组件和Hooks总结
  • springboot项目单独对数据源配置加解密
  • 编程基础之字符串——过滤多余的空格
  • B3844 [GESP样题 二级] 画正方形
  • CPP多线程2:多线程竞争与死锁问题
  • 复合机器人食品分拣生产线:一体化控制系统引领高效柔性新食代
  • 硬核北京 | 2025世界机器人大会“破圈”,工业智能、康养科技…… 亦庄上演“机器人总动员”
  • Java 多线程教程
  • 心路历程-三个了解敲开linux的大门
  • 第三十七天(js前端数据加密和混淆)
  • 设计模式之静态代理
  • 拒绝造轮子(C#篇)使用SqlSugar实现数据库的访问
  • KingbaseES高可用架构深度解析——从读写分离到异地灾备的全方位守护