目录

  • 题目
  • 思路
  • 暴力法
  • 动态规划
  • 双指针法
  • 单调栈

题目

给定 n 个非负整数表示每个宽度为 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
0 <= n <= 3 * 10^4
0 <= height[i] <= 10^5

思路

对于数组中的每个元素,最高水位为:两边最大高度的较小值 - 当前高度的值
用伪代码描述:
water[i] = min(left,right) - height[i];
(有一说一,没想到是对每个元素进行计算的,以为是找到几个较高点,然后划分区间分别计算。)
这样问题就可以化简为寻找每个元素右边最大的值和左边最大的值了。
没想到这个,我感觉很难往下做下去。

暴力法

使用暴力法,对于遍历数组的时候,对应的元素找左右最大值,顺着上面的思路来一遍:
显然时间复杂度是O(n^2)的

class Solution {public:int trap(vector<int>& height){int ans = 0;int n = height.size();for(int i = 0; i < n; i++){//找左右最大值int left_max = 0, right_max = 0;for(int j = i; j >= 0; j--)left_max = max(left_max,height[j]);for(int j = i; j < n; j++)right_max = max(right_max,height[j]);//累积此位置的雨水ans += min(left_max,right_max) - height[i];}return ans;}
};

动态规划

之前,对于每个元素的左右最大值我们是在遍历该元素的时候找的。可以预先找到,存储下来,计算雨水的时候直接查询即可。
需要预先构造出两个数组,用来存放。
填充极大值数组的时候要记住:
从左向右遍历,每次更新左极大值,当前元素的左极大值只和当前元素和左边一个元素的左极大值有关。
即:left_max[i] = max(height[i],left_max[i - 1]);
从右向左遍历,每次更新右极大值,当前元素的右极大值只和当前元素和右边一个元素的右极大值有关
即:right_max[i] = max(height[i],right_max[i + 1]);
这个方法有两个细节:
1、考虑输入数组为空
2、初始化左右极大值数组的第一个元素以及遍历的起点

显然,时间复杂度是O(n)的。是遍历了3次原数组。

class Solution {public:int trap(vector<int>& height){if(height.empty()) return 0;int ans = 0;int n = height.size();vector<int> left_max(n);vector<int> right_max(n);//初始化//第一个元素的左边没有元素,所以左极大值就是本身left_max[0] = height[0];//最后一个元素的右边没有元素,所以右极大值就是本身right_max[n - 1] = height[n - 1];//从左向右遍历,每次更新左极大值,当前元素的左极大值只和当前元素和左边一个元素的左极大值有关for(int i = 1; i < n; i++)left_max[i] = max(height[i],left_max[i - 1]);//从右向左遍历,每次更新右极大值,当前元素的右极大值只和当前元素和右边一个元素的右极大值有关for(int i = n - 2; i >= 0; i--)right_max[i] = max(height[i],right_max[i + 1]);for(int i = 0; i < n; i++){ans += min(left_max[i],right_max[i]) -height[i];}return ans;}
};

双指针法

动态规划是遍历了两次,使用双指针可以实现只遍历一次就实现填充左右极大值数组。
感觉有个解释解释的非常好:
https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode/327718

1、明确变量意义

left_max:左边的最大值,它是从左往右遍历找到的
right_max:右边的最大值,它是从右往左遍历找到的
left:从左往右处理的当前下标
right:从右往左处理的当前下标

2、明确已知定理:

定理一:在某个位置i处,它能存的水,取决于它左右两边的最大值中较小的一个。
定理二:当我们从左往右处理到left下标时,左边的最大值left_max对它而言是可信的,但right_max对它而言是不可信的。(见下图,由于中间状况未知,对于left下标而言,right_max未必就是它右边最大的值)
定理三:当我们从右往左处理到right下标时,右边的最大值right_max对它而言是可信的,但left_max对它而言是不可信的。

3、得到具体细节

对于位置left而言,它左边最大值一定是left_max,右边最大值“大于等于”right_max,这时候,如果left_max<right_max成立,那么它就知道自己能存多少水了。无论右边将来会不会出现更大的right_max,都不影响这个结果。 所以当left_max<right_max时,我们就希望去处理left下标,反之,我们希望去处理right下标。

4、知道了这些,就可以写代码了:

class Solution {public:int trap(vector<int>& height){if(height.empty()) return 0;int ans = 0;int n = height.size();int left = 0,right = n - 1;int left_max = height[left], right_max = height[right];while(left <= right){if(left_max < right_max){//还要判断left_max与当前的值谁大,如果当前值大,就不需要累积了。if(left_max < height[left]) left_max = height[left];else    ans += (left_max - height[left]);left++;}else{//还要判断left_max与当前的值谁大,如果当前值大,就不需要累积了。if(right_max < height[right]) right_max = height[right];else    ans += (right_max - height[right]);right--;}}return ans;}
};

单调栈

尽管之前写过接近8题的单调栈的题目,这一题我使用单调栈的思路感觉并不是很清晰。而且粗略看了官方题解也没理解具体细节。
下面两个单调栈的讲法都比较好:
我下面使用的图也是从这两个地方嫖的。
https://leetcode-cn.com/problems/trapping-rain-water/solution/42-jie-yu-shui-shuang-zhi-zhen-dong-tai-gui-hua-da/
https://leetcode-cn.com/problems/trapping-rain-water/solution/dan-diao-zhan-jie-jue-jie-yu-shui-wen-ti-by-sweeti/
单调栈计算雨水的方式是按照行计算的:


还有两个个需要注意的地方:
1、我们构建的是单调递减栈,一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
如下图所示:

2、遇到相同高度的柱子怎么办。
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。

接下来就可以套用模板写代码了
这里借鉴了代码随想录的冗余代码,能够较好地理解for循环中三个情况具体处理。

class Solution {public:int trap(vector<int>& height){if(height.empty()) return 0;int ans = 0;int n =height.size();vector<int> st;for(int i = 0; i < n; i++){//如果栈为空,或者栈顶元素大于height[i]入栈if( st.empty() || height[i] < height[st.back()]){st.emplace_back(i);}//如果栈不为空且栈顶元素==height[i],更新栈顶元素,取更右边的else if(!st.empty() && height[i] == height[st.back()]){st.pop_back();st.emplace_back(i);}//如果栈不为空,且栈顶元素小于height[i]else{while(!st.empty() && height[i] > height[st.back()]){int mid = st.back();st.pop_back();if(!st.empty()){int H = min(height[st.back()],height[i]) - height[mid];int W = i - st.back() -1;       //i与st.back()之间的宽度ans += H * W;}}st.emplace_back(i);}}return ans;}
};

leetcode 42. 接雨水 思考分析(暴力、动态规划、双指针、单调栈)相关推荐

  1. leetcode 322. 零钱兑换 思考分析

    目录 1.题目 2.思路分析 3.参考链接 1.题目 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成总 ...

  2. leetcode 47. 全排列 II 思考分析

    题目 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列. 思考分析以及代码 这一题和前面的做过的两个题目有所关联: leetcode 46. 全排列 思考分析 再加上lee ...

  3. leetcode 491. 递增子序列 思考分析

    题目 给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2. 说明: 给定数组的长度不会超过15. 数组中的整数范围是 [-100,100]. 给定数组中可能包含重复数字 ...

  4. leetcode 617. 合并二叉树 思考分析

    题目 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠. 你需要将他们合并为一个新的二叉树.合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否 ...

  5. leetcode 90. 子集 II 思考分析

    与本题相关联的题目解析: leetcode 78. 子集 思考分析 leetcode 40. 组合总和 II思考分析 题目 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集 ...

  6. LeetCode 101. 对称二叉树 思考分析

    题目 给定一个二叉树,检查它是否是镜像对称的. 例如,二叉树 [1,2,2,3,4,4,3] 是对称的. 1 / 2 2 / \ / 3 4 4 3 但是下面这个 [1,2,2,null,3,null ...

  7. leetcode 202. 快乐数 思考分析(哈希集合与双指针解)

    1.题目 编写一个算法来判断一个数 n 是不是快乐数. 「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变 ...

  8. leetcode 39. 组合总和 思考分析

    目录 1.题目 2.思考分析 3.未经优化代码 4.剪枝优化 1.题目 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 ...

  9. leetcode 42 接雨水 单调栈

    接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水. 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下 ...

最新文章

  1. 巨大的需求之下 人工智能如何更快落地?
  2. 全球厂商已向自动驾驶投入800亿美元 依然群龙无首 | 厚势
  3. SCVMM2008的P2V后 MAC地址冲突
  4. github的角色和人员结构
  5. Android NDK引用预编译的动态链接库
  6. 从Python 2切换到Python 3:您需要了解的内容
  7. C语言单链表定义及各类操作
  8. 啊哈算法-擒贼先擒王(并查集)
  9. 很多人都忽视了账号基建重要性
  10. 预习 Delphi 2009 中的新功能 - JSON (二) : 如何在 Delphi 中使用 json
  11. 10的多少次方 oracle_Oracle SQL計算平方根、立方根、次方
  12. 互联网定律及效应汇编
  13. 计算机组装拆卸 心得,学习组装电脑的心得体会怎么写?
  14. Oracle EBS AutoConfig详解
  15. Oracle 递归查询SQL
  16. R语言绘制PCoA图
  17. 《修炼之道:互联网产品从设计到运营》荣获“2012最受读者喜爱的IT人文类图书奖”!
  18. cocoscreator利用中点位移算法制作闪电
  19. 《腾讯数字生活报告2019》发布,互联网时代新马斯洛需求金字塔预示什么?
  20. 层次分析法分析国家综合国力

热门文章

  1. 盘点和程序员相关的那些事,让你不再被割韭菜,薅羊毛!
  2. MyEclipes+html+jsp+mysql实现一个物流信息网
  3. javaScript中const,var,let区别与用法详解
  4. Linux常用命令(知道啦就赶紧收藏吧)
  5. js基础---数组方法
  6. 关于form标签,你该知道
  7. python 内置标准库socketserver模块的思考
  8. jQuery数据转换与提交
  9. 基于 Docker 打造前端持续集成开发环境
  10. 子元素的margin-top会影响父元素