接雨水解法详解:

题目:

基本思路:从图上可以看出要想接住雨水,必须是凹字形的,也就是当前位置的左右两边必须存在高度大于它的地方,所以我们要想知道当前位置最多能存储多少水,只需找到左边最高处max_left和右边最高处max_right,取他们两个较小的那边计算即可(短板效应)。

其实接下来的解法要解决的问题就是如何找到max_rightmax_left

不过我们首先来看一个无法AC的解法:

解法一:按行

按行顾名思义就是一行一行地进行计算,首先我们计算第一行,设置一个变量temp临时存储当前接的水和开始标志isStart,当碰到第一个高度大于等于行数的位置时,temp置0,开始计算,接下来如果碰到小于行数的位置时,temp+1,碰到大于行数的位置时,temp置0,继续往后遍历,如此遍历完一行继续遍历下一行。

class Solution {
public int trap(int[] height) {
int sum = 0;
int max = getMax(height);//找到最大的高度,以便遍历。
for (int i = 1; i <= max; i++) {
boolean isStart = false; //标记是否开始更新 temp
int temp_sum = 0;
for (int j = 0; j < height.length; j++) {
if (isStart && height[j] < i) {
temp_sum++;
}
if (height[j] >= i) {
sum = sum + temp_sum;
temp_sum = 0;
isStart = true;
}
}
}
return sum;
}
private int getMax(int[] height) {
int max = 0;
for (int i = 0; i < height.length; i++) {
if (height[i] > max) {
max = height[i];
}
}
return max;
}
}

不过此解法会在最后两个测试用例处,TLE掉,我们只需了解一下这种思想,medium难度的题还是可以过的。

解法二:按列

这个解法就是我们刚开始所说思路的最朴素的解法了,要求i位置接水量,只需找到i左边最高位置的高度max_left和右边最高位置的高度max_right,然后取较小的那个min_height=min(max_right,max_left),最后计算i位置接水量:min_height-height[i],当然如果min_height<height[i],就不用计算啦。

class Solution {
public int trap(int[] height) {
int ans=0;
for(int i=1;i<height.length-1;i++){//第一个和最后一个位置肯定存不了水
int max_left=0;
for(int j=i-1;j>=0;j--){//找到当前位置左边最高处
max_left=Math.max(max_left,height[j]);
}
int max_right=0;
for(int j=i+1;j<height.length;j++)//找到当前位置右边最高处
max_right=Math.max(max_right,height[j]);
int min_high=Math.min(max_right,max_left);
if(min_high>height[i]){
ans+=min_high-height[i];
}
}
return ans;
}
}

解法三:动态规划

我们知道解法二每到一个位置都会遍历左右两边来寻找它的左右最高处,这样就会导致O(n^2)的时间复杂度,我们能不能事先就找好每个位置对应的max_leftmax_right,显而易见是可以的,所以我们需要声明两个数组max_leftmax_right来存储每个位置对应的左右最高点,因为我们只在它左右两边寻找,我们可以写出当前位置imax_left[i]=max(max_left[i-1],height[i-1]),右边同理,接下来就是和解法二一样了。

class Solution {
public int trap(int[] height) {
int[] max_left=new int[height.length];
int[] max_right=new int[height.length];
int ans=0;
for(int i=1;i<height.length-1;i++){
max_left[i]=Math.max(max_left[i-1],height[i-1]);
}
for(int i=height.length-2;i>=1;i--){
max_right[i]=Math.max(max_right[i+1],height[i+1]);
}
for(int i=1;i<height.length-1;i++){
int min_height=Math.min(max_right[i],max_left[i]);
if(min_height>height[i]){
ans+=min_height-height[i];
}
}
return ans;
}
}

解法四:双指针

从动态规划解法可以看出,只要max_left[i]<max_right[i],积水的高度由max_left[i]决定,也就是积水的高度是由较低的那边决定,所以此时我们应该继续由较低->较高那个方向遍历,直到此时较低的这边发现比另一边更高的位置,再转换方向,这样我们就可以一次遍历且只需两个指针便可完成计算。

  • 初始化 left 指针为 0 并且 right 指针为 size-1
  • While left<right, do:

    If height[left] < height[right]

    If height[left]≥left_max, 更新 left_max

    Else left_max−height[left] 到ans

    left = left + 1.
  • Else

    If height[right]≥right_max, 更新 right_max

    Else 累加 right_max−height[right] 到 ans

    right = right - 1.
int trap(vector<int>& height)
{
int left = 0, right = height.size() - 1;
int ans = 0;
int left_max = 0, right_max = 0;
while (left < right) {
if (height[left] < height[right]) {
height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]);
++left;
}
else {
height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]);
--right;
}
}
return ans;
}

解法五:单调栈

思路:单调栈可以保证栈底到栈顶是单调递减的,也就是说栈顶元素可以由它前一个元素所界定,我们遍历数组时可以维护一个单调栈来存储索引,当当前元素小于栈顶元素时,入栈,当当前元素大于等于栈顶元素时,此时栈顶元素的接水量就取决于其前一个元素当前元素较小的那个,栈顶元素出栈。

算法:

  • 使用栈来存储条形块的索引下标。
  • 遍历数组:
    • 当栈非空且 height[current]>height[st.top()]
    • 意味着栈中元素可以被弹出。弹出栈顶元素
    • 计算当前元素和栈顶元素的距离,准备进行填充操作

      distance=current−st.top()−1
  • 找出界定高度
    • bounded_height=min(height[current],height[st.top()])−height[top]
    • 往答案中累加积水量ans+=distance×bounded_height
  • 将当前索引下标入栈
  • 将 current 移动到下个位置
int trap(vector<int>& height)
{
int ans = 0, current = 0;
stack<int> st;
while (current < height.size()) {
while (!st.empty() && height[current] > height[st.top()]) {
int top = st.top();
st.pop();
if (st.empty())
break;
int distance = current - st.top() - 1;
int bounded_height = min(height[current], height[st.top()]) - height[top];
ans += distance * bounded_height;
}
st.push(current++);
}
return ans;
}

总结:

这种多解法的题目可以尽量去了解它的每一种解法,这对扩展自己的解题思维有很大的帮助,其实这个题目这么多解法大部分都是在解决怎么高效找出左右两边最高点,时间复杂度由O(n^2)提升到O(n),空间复杂度也由O(n)->O(1),所以有时候很多方法都是由暴力逐渐改善的,本人表达可能词不达意,还请谅解,如有任何问题,请留言指出,不甚感激。

参考出处

LeetCode(42.接雨水)多解法详解的更多相关文章

  1. leetcode#42 Trapping rain water的五种解法详解

    leetcode#42 Trapping rain water 这道题十分有意思,可以用很多方法做出来,每种方法的思想都值得让人细细体会. 42. Trapping Rain WaterGiven n ...

  2. Java实现 LeetCode 42 接雨水

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

  3. [LeetCode]42. 接雨水(双指针,DP)

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

  4. leetcode 42. 接雨水 JAVA

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

  5. HDU 4622 Reincarnation Hash解法详解

    今天想学字符串hash是怎么弄的.就看到了这题模板题 http://acm.hdu.edu.cn/showproblem.php?pid=4622 刚开始当然不懂啦,然后就上网搜解法.很多都是什么后缀 ...

  6. Leetcode 42.接雨水

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

  7. Leetcode 42 接雨水 双指针

    地址 https://leetcode-cn.com/problems/trapping-rain-water/ 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能 ...

  8. LeetCode 42. 接雨水(Trapping Rain Water)

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

  9. C++旋转数组(三种解法详解)

    题目描述 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数. 附加要求 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题. 你可以使用空间复杂度为 O(1) 的 原地 ...

随机推荐

  1. [面试专题]Web缓存详解

    Web缓存详解 标签(空格分隔): 缓存 缓存之于性能优化 请求更快:通过将内容缓存在本地浏览器或距离最近的缓存服务器(如CDN),在不影响网站交互的前提下可以大大加快网站加载速度. 降低服务器压力: ...

  2. 用 git 钩子,检测代码规范性(eslint、standard)

    最终实现效果说明:用 git commit 提交代码之前,利用 pre-commit git 钩子,实现代码规范检测(eslint.standard 规范),符合规范之后才可以提交到 git 仓库.这 ...

  3. sentinel 规则持久化到nacos

    问题描述 Sentinel Dashboard中添加的规则是存储在内存中的,只要项目一重启规则就丢失了 此处将规则持久化到nacos中,在nacos中添加规则,然后同步到dashboard中: 后面研 ...

  4. mongodb忘记密码处理步骤

    mongodb忘记密码的处理办法较MySQL等数据库而言方法显得更加暴力,处理方式如下: 1.  修改mongodb的配置文件 mongodb的配置文件一般可以通过查看进程的方式查看文件名,例如: p ...

  5. 【原创】(五)Linux进程调度-CFS调度器

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  6. JVM04——七个GC垃圾收集器,一个都不能少

    了解了JVM内存区域与垃圾回收算法,今天将为各位带来关于垃圾收集器的知识.关注我的公众号「Java面典」了解更多 Java 相关知识点. Java 堆内存被划分为新生代和老年代两部分,因此 JVM 通 ...

  7. WPF转换器之值转换器

    WPF有两转转换器,一种是值转换器,另一种多值转换器,在开发过程中经常会从数据拉一些数据过来,比如存储性别的时候往往会用0或1,但在界面上肯定是要显示男或女,那么这个时候就可以用上值转换器 编写转换器 ...

  8. echarts legend文字配置多个颜色(转)

    困扰很久的问题终于解决了 oh yea! echarts legend文字配置多个颜色legend: {data: [{name:‘直接访问’,icon : ‘circle’,textStyle: { ...

  9. niginx:duplicate MIME type "text/html" in nginx.conf 错误(转载)

    把nginx升级到最新以后,发现用原来的配置启动的时候会提示: duplicate MIME type "text/html" in /usr/local/nginx/conf/n ...

  10. java虚拟机学习记录(内存划分、垃圾回收、类加载等机制)

    一直以来觉得虚拟机是Java最难的一部分,涉及最底层的原理,学起来难度很大,而且工作中基本上用不到这些原理,所以对这部分“敬而远之”.现如今工作五年了,从Java基础到算法.数据结构.网络.数据库.设 ...