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

接雨水问题解析:双指针与单调栈解法

接雨水问题解析:双指针与单调栈解法

问题描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子下雨之后能接多少雨水。这个问题可以形象地理解为:在由一系列柱子组成的"地形"上方下雨,雨水会积存在柱子之间的凹陷处,我们需要计算这些凹陷处总共能存储多少雨水。

示例

示例 1:

高度图:[0,1,0,2,1,0,1,3,2,1,2,1]
图形表示:■■    ■ ■■ ■ ■■■ ■ ■ ■ ■
0 1 0 2 1 0 1 3 2 1 2 1

输出:6
解释:可以接 6 个单位的雨水(■表示柱子,~表示雨水):

■~~~~■ ■
■ ■~■■■ ■ ■ ■ ■

示例 2:

高度图:[4,2,0,3,2,5]
图形表示:
■
■     ■
■ ■ ■ ■
■ ■ ■ ■
■ ■ ■ ■
4 2 0 3 2 5

输出:9
解释:可以接 9 个单位的雨水:

■~~~~~■
■ ■~■ ■
■ ■ ■ ■
■ ■ ■ ■

解法一:双指针法(最优解)

思路分析

双指针法是一种高效且空间复杂度低的解决方案。其核心思想是:

  1. 使用左右两个指针分别从数组两端向中间移动,类似于快速排序的分区操作
  2. 维护左右两边的最大值(lmax和rmax),这两个值决定了当前可以接多少雨水
  3. 根据左右最大值中的较小值来决定计算哪一侧的雨水,因为水位高度由较低的一侧决定

算法步骤详解

  1. 初始化左右指针 l = 0r = height.size() - 1
  2. 初始化左右最大值 lmax = 0rmax = 0
  3. l < r 时循环:
    • 更新左右最大值:
      • lmax = max(lmax, height[l])(记录从左到目前位置的最大高度)
      • rmax = max(rmax, height[r])(记录从右到目前位置的最大高度)
    • 比较当前左右指针处的高度:
      • 如果 height[l] < height[r]
        • 计算左指针处的雨水量:lmax - height[l](因为右边有更高的柱子)
        • 将结果累加到总雨水数中
        • 左指针右移(l++
      • 否则:
        • 计算右指针处的雨水量:rmax - height[r](因为左边有更高的柱子)
        • 将结果累加到总雨水数中
        • 右指针左移(r--
  4. 返回累计的雨水量

代码实现

class Solution {
public:int trap(vector<int>& height) {if(height.empty()) return 0;  // 边界条件处理int l = 0, r = height.size() - 1;int ans = 0;int lmax = height[l];  // 初始化为第一个元素int rmax = height[r];  // 初始化为最后一个元素while(l < r) {// 更新左右最大值lmax = max(lmax, height[l]);rmax = max(rmax, height[r]);// 决定移动哪一侧指针if(height[l] < height[r]) {ans += lmax - height[l];  // 计算当前列的雨水量++l;} else {ans += rmax - height[r];  // 计算当前列的雨水量--r;}}return ans;}
};

复杂度分析

  • 时间复杂度:O(n),只需遍历一次数组
  • 空间复杂度:O(1),只使用了常数个额外变量(l, r, ans, lmax, rmax)

实际应用场景

这种解法非常适合处理大规模数据,比如:

  • 城市建筑群的雨水收集系统设计
  • 地形分析中的积水区域计算
  • 游戏开发中的物理引擎水位模拟

解法二:单调栈法

思路分析

单调栈法通过维护一个单调递减的栈来计算雨水:

  1. 遍历高度数组
  2. 维护一个单调递减栈(存储索引),栈底到栈顶高度递减
  3. 当当前高度大于栈顶高度时,说明可能形成"凹槽"可以接住雨水
  4. 计算凹槽的宽度和高度,进而计算雨水量并累加

详细步骤

  1. 初始化一个空栈和结果变量ans=0
  2. 遍历高度数组(i从0到n-1):
    • 当栈不为空且当前高度height[i] > 栈顶高度height[st.top()]:
      • 弹出栈顶元素作为凹槽底部(top = st.top())
      • 如果栈为空,说明没有左边界,无法形成凹槽,跳出循环
      • 计算凹槽宽度:当前索引i - 新栈顶索引l - 1
      • 计算凹槽高度:min(height[l], height[i]) - height[top]
      • 将宽度×高度加到ans中
    • 将当前索引i压入栈中
  3. 返回累计的雨水量ans

代码实现

class Solution {
public:int trap(vector<int>& height) {int ans = 0;stack<int> st;  // 存储柱子的索引int n = height.size();for(int i = 0; i < n; i++) {// 当当前柱子高于栈顶柱子时,可能存在凹槽while(!st.empty() && height[i] > height[st.top()]) {int top = st.top();  // 凹槽底部位置st.pop();  // 弹出栈顶if(st.empty()) break;  // 没有左边界int l = st.top();  // 左边界位置int curwidth = i - l - 1;  // 凹槽宽度int curheight = min(height[l], height[i]) - height[top];  // 凹槽高度ans += curwidth * curheight;  // 计算当前凹槽的雨水量}st.push(i);  // 将当前柱子压入栈中}return ans;}
};

复杂度分析

  • 时间复杂度:O(n),每个元素最多被压入和弹出栈一次
  • 空间复杂度:O(n),最坏情况下(如单调递减的数组)栈的大小为n

适用场景

单调栈法特别适合:

  • 需要直观理解雨水形成过程的场景
  • 需要分步计算每个凹槽雨水量的情况
  • 教学和算法理解阶段

方法对比与总结

方法时间复杂度空间复杂度优点缺点
双指针法O(n)O(1)空间效率高,代码简洁理解起来稍抽象
单调栈法O(n)O(n)直观,易于理解空间消耗较大

选择建议

  1. 面试场景:优先使用双指针法,展示对最优解的理解
  2. 教学场景:可以先讲解单调栈法,再引入双指针优化
  3. 实际应用:根据内存限制选择,内存紧张时用双指针法

扩展思考

  1. 如何修改算法计算二维平面的接雨水问题?
  2. 如果柱子不是直立的,而是有倾斜角度,算法该如何调整?
  3. 如何实时计算动态变化的地形中的积水量?

理解这两种经典解法不仅有助于解决接雨水问题,也为处理其他类似的区间计算问题(如最大矩形面积、柱状图中最大矩形等)提供了思路框架。


文章转载自:

http://al5XMnFk.mtbsd.cn
http://V2svtVyx.mtbsd.cn
http://gsreuytN.mtbsd.cn
http://XVdvz0JF.mtbsd.cn
http://K5Jh3qmg.mtbsd.cn
http://amOvF1rq.mtbsd.cn
http://0pK79Jgq.mtbsd.cn
http://Mxo8Pr2S.mtbsd.cn
http://15jCS7sR.mtbsd.cn
http://OYafAn5J.mtbsd.cn
http://sIdyKVAO.mtbsd.cn
http://gJCkisdN.mtbsd.cn
http://zTaG3Ho3.mtbsd.cn
http://y1ABLHK9.mtbsd.cn
http://Q91UT77Y.mtbsd.cn
http://5NmWLEqs.mtbsd.cn
http://LhXwVJs0.mtbsd.cn
http://xBy17nrL.mtbsd.cn
http://VXKvcpav.mtbsd.cn
http://tk0SF70i.mtbsd.cn
http://g5qhn5XI.mtbsd.cn
http://W0J6cwfY.mtbsd.cn
http://c1El8Pqx.mtbsd.cn
http://hzzHqWIt.mtbsd.cn
http://PrtTDCqH.mtbsd.cn
http://OMtWYq8F.mtbsd.cn
http://wTx35Epf.mtbsd.cn
http://eIyxXj8j.mtbsd.cn
http://io7xq46J.mtbsd.cn
http://rPLH09rM.mtbsd.cn
http://www.dtcms.com/a/370635.html

相关文章:

  • Kafka Exactly-Once 语义深度解析与性能优化实践指南
  • spring-ai-alibaba-deepresearch 学习(十三)——ResearcherNode
  • 2、数学与经济管理
  • 使用 Shell 脚本监控服务器 IOWait 并发送邮件告警
  • Python数据可视化科技图表绘制系列教程(六)
  • [Upscayl图像增强] docs | 前端 | Electron工具(web->app)
  • 同态加密库(Google FHE)
  • Qt自定义列表项与QListWidget学习
  • MySQL 高可用方案之 MHA 架构搭建与实践
  • 天津大学2024-2025 预推免 第一批机试题目纯暴力题解
  • 金属也有“记忆力”?—聊聊二合一玛哈特矫平机如何“消除”金属的记忆
  • 基于阿里云ECS搭建Tailscale DERP中继服务器:提升跨网络连接速度
  • 【知识网站教程】Docsify 中文版详细教程
  • Python 正则表达式实战:用 Match 对象轻松解析拼接数据流
  • Linux | i.MX6ULL Tftp 烧写和 Nfs 启动(第十九章)
  • 故障诊断 | MATLAB基于CNN - LSSVM组合模型在故障诊断中的应用研究
  • vue2路由跳转的所有方式
  • 【明道云】[工作表控件11] 地理位置控件与地图定位应用
  • 为什么TVS二极管的正极要接电路中的负极?-ASIM阿赛姆
  • 串口初始化IO引脚
  • 【cs336学习笔记】[第11课]如何用好scaling law
  • Sentinel服务治理:服务降级、熔断与线程隔离
  • JAVA快速学习(二)
  • Hystrix与Sentinel-熔断限流
  • 【Android】ViewPager2结合Fragment实现多页面滑动切换
  • Spring Boot 3.x 的 @EnableAsync应用实例
  • Android Audio Patch
  • java社交小程序源码支持APP多端springboot部署与功能模块详解
  • 安装es和kibana
  • phpMyAdmin文件包含漏洞复现:原理详解+环境搭建+渗透实战(vulhub CVE-2018-12613)