题目:42. 接雨水

给定 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 个单位的雨水(蓝色部分表示雨水)。

第一种解法:暴力破解,一列一列算

思路

使用双层循环,在遍历每一根柱子的同时,求出第i柱子左右两边高度最高的柱子分别是多少,然后根据两边高度最高柱子中较低的那根柱子去求出第i根柱子最多能接多少雨水。看下图做参考理解

步骤

  1. 外层循环遍历每根柱子

    1. 内层嵌套循环①,求第i根柱子左边高度最高的柱子,记录下来max_left
    2. 内层嵌套循环②,求第 i根柱子右边高度最高的柱子,记录下来max_right
    3. max_leftmax_right进行比较,算出两边最高的柱子中较低的一根,因为它类似于木桶效应,桶中水的高度总是由较低的木板的高度决定
    4. 如果此时两边柱子中较低的一根柱子比当前遍历的柱子i要高,就说明这根柱子能盛放住雨水
    5. 柱子i能盛放的雨水量就等于它两边最高柱子中较低的一根柱子的高度减去当前柱子的高度,得出结果 将其加入到计算的雨水总量中
  2. 返回能接雨水的总量

代码

  • 根据代码进一步理解
// 暴力破解,按列求
public int trap(int[] height) {
int sum = 0;
int len = height.length;
for (int i = 1; i < len - 1; i++) {
int max_left = 0;
int max_right = 0;
int min_height = 0;
//求元素左边最高的柱子
for (int j = i - 1; j >= 0; j--) {
if (height[j] > max_left)
max_left = height[j];
} //求元素右边最高的柱子
for (int k = i + 1; k < len; k++) {
if (height[k] > max_right)
max_right = height[k];
}
//求出较低的柱子
min_height = Math.min(max_left,max_right);
//比较计算
if (min_height > height[i])
sum += min_height - height[i];
}
return sum;
}

优化

​ 外层循环是从左到右遍历每根柱子,而内层求左侧最高的柱子的时候又从左到右循环,显然是有冗余的,我们可以使用max_left直接标记左侧最高的柱子,每次只需要比较max_left与前一根柱子的高度大小,而不需要再进行一次遍历

代码如下,改动很小,只将原来求元素左边最高的柱子的for循环改变为if语句

// 暴力破解优化
public int trap(int[] height) {
int sum = 0;
int max_left = 0;
for (int i = 1; i < height.length - 1; i++) {
//求元素左边最高的柱子
if (height[i-1] > max_left)
max_left = height[i-1]; //求元素右边最高的柱子
int max_right = 0;
for (int k = i + 1; k < height.length; k++) {
if (height[k] > max_right)
max_right = height[k];
}
//求出较低的柱子
int min_height = Math.min(max_left,max_right);
//比较计算
if (min_height > height[i])
sum += min_height - height[i];
}
return sum;
}

参照这个思路,该如何标记遍历时右侧最高的柱子呢?

那么我们就来看下一种解法思路

第二种解法:动态规划求解

思路

我们可以创建两个数组分别记录每根柱子左侧最高的柱子以及右侧最高的柱子

就像我们第一种解法最后的优化思路一样,第i根柱子左侧最高的柱子就等于第i-1根柱子与第i-1根柱子前最高的柱子中较高的一根,也就是max_left[i] = Math.max(max_left[i-1],height[i-1])

那第i根柱子右侧最高的柱子应该怎样算?

我们可以倒序遍历,从右往左遍历柱子,用数组记录下来第i根柱子右侧最高的柱子是那根,类似的,第i+1根柱子与第i+1根柱子右边较高的一根就是第i根 柱子右侧高度最高的柱子,也就是max_right[i] = Math.max(max_right[i+1], height[i+1])

要谨慎对待边界问题

代码

    // 动态规划求解
public int trap3(int[] height) {
int sum = 0;
int[] max_left = new int[height.length];
int[] max_right = new int[height.length]; // 每根柱子对应的左边最高的柱子 (注: 并没有使用上一解法的优化做法,而是使用纯种的动态规划,循序渐进,希望可以便于理解)
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 >= 0; i--) {
max_right[i] = Math.max(max_right[i+1], height[i+1]);
} int min_height = 0;
for (int i = 1; i < height.length; i++) {
min_height = Math.min(max_left[i],max_right[i]);
if (min_height > height[i])
sum += min_height - height[i];
}
return sum;
}

进行优化的思路

其实,我们在使用数组的过程中 只使用了一个值,这点我们在第一种解法的优化思路中已经说过了,所以说我们只需要各用一个值来记录左右两边的最高柱子就可以,但是循环是从左往右遍历的,所以左侧最高柱子max_left好记录,而右侧我们又想降低空间复杂度,不使用数组来记录最高柱子,那我们应该如何来解决呢?往下看

第三种解法:双指针解决

思路

我们可以使用双指针来很好的解决这个问题,使用leftright分别指向数组两端

我们重新理一下思路,这里不太好想

一根柱子能盛放的水的多少,取决于它左右两边最高的柱子中较低的一根

在遍历的过程中,从左往右遍历我们能知道max_left是确定的,从右往左遍历我们能知道max_right是确定的,而只要我们确定了这两个值中较小的值是多少,我们就可以确定这根柱子能盛多少水,那么另一个较大的值无论多大都不会影响结果

所以我们可以从两端分别进行遍历,当第left-1根柱子高度小于第right+1根柱子高度时left向右移,我们计算第left根柱子能盛水多少,当第left-1根柱子大于第right+1根柱子高度时right向左移,计算第right根柱子能盛水多少。

我们可以试着这样去理解,从一开始,如果第left-1根柱子高度比第right+1根柱子低,那么max_left就会一直比max_right低,直到通过left指针向右不断移动到了一个使得第left-1根柱子的高度比第right+1根柱子的高度高的情况,那么此时max_left就比max_right更高,我们就可以将right指针向左移。。。这样一次一次通过left-1right+1的比较以及左右指针的交替使用,我们就可以逐一确定所有柱子能够盛放水的多少

  • 双指针的解法非常巧妙,大家请多思考

代码

// 双指针求解
public int trap(int[] height) {
int sum = 0;
int max_left = 0;
int max_right = 0;
int left = 1;
int right = height.length - 2; for (int i = 1; i < height.length - 1; i++) {
if (height[left-1] < height[right+1]) {
max_left = Math.max(max_left,height[left-1]);
int min = max_left;
if (min > height[left])
sum += min - height[left];
left++;
}else {
max_right = Math.max(max_right,height[right+1]);
int min = max_right;
if (min > height[right])
sum += min - height[right];
right--;
}
} return sum;
}

第四种解法:单调栈解法

  • 除了使用双指针进行优化,我们还可以使用特殊的数据结构来解决

思路

积水存在的原因是什么?

当这根柱子的两侧有比它更高的柱子存在,这根柱子所在的地方就形成了低洼,就能够存储水源

我们从左侧开始遍历,如果一个元素比它的栈顶元素小,就入栈

反之,如果遍历到一个元素,此元素比栈顶元素大,就说明栈顶元素两侧可能形成了高度差,栈顶元素弹出栈,此元素与新的栈顶元素进行判断与计算,直到此元素不大于此时栈顶元素,然后此元素入栈。

代码

// 单调递减栈解法
public int trap1(int[] height) {
int sum = 0;
int cur = 0; //指向的是当前元素
Deque<Integer> stack = new ArrayDeque<>();
while (cur < height.length) {
while (!stack.isEmpty() && height[cur] > height[stack.peek()]) {
//栈顶元素出栈并赋值给 h
int h = stack.pop();
//栈顶元素出栈之后再次判断栈是否为空,如果为空,直接进行下次循环
if (stack.isEmpty())
break;
// 判断当前元素与此时栈顶之间的距离
int distance = cur - stack.peek() - 1;
// 求出当前元素与此时栈顶元素中高度较小的一边
int min_h = Math.min(height[cur],height[stack.peek()]);
// 求出之前栈顶也就是 h 所能盛放水的多少
sum += distance * (min_h - height[h]);
}
stack.push(cur);
cur++;
}
return sum;
}

LeetCode---42. 接雨水 (hard)的更多相关文章

  1. Java实现 LeetCode 42 接雨水

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

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

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

  3. leetcode 42. 接雨水 JAVA

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

  4. Leetcode 42.接雨水

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

  5. Leetcode 42 接雨水 双指针

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

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

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

  7. LeetCode(42.接雨水)多解法详解

    接雨水解法详解: 题目: 基本思路:从图上可以看出要想接住雨水,必须是凹字形的,也就是当前位置的左右两边必须存在高度大于它的地方,所以我们要想知道当前位置最多能存储多少水,只需找到左边最高处max_l ...

  8. LeetCode 42接雨水 按行求解(差分+排序)

    按行求解的思路比较清晰明了,但是这个方法的复杂度高达O(heightSize*sum(height[i])),几乎高达O(N^2). 但是也并不是不可以解决,经观察我们可以发现,这个算法的缺点在于要遍 ...

  9. 每日一题 LeetCode 42.接雨水 【双指针】

    题目链接 https://leetcode-cn.com/problems/trapping-rain-water/ 题目说明 题解 主要方法:双指针 + 正反遍历 解释说明: 正向遍历:先确定池子左 ...

  10. LeetCode:接雨水【42】

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

随机推荐

  1. 一个C#开发编写Java框架的心路历程

    前言 这一篇絮絮叨叨,逻辑不太清晰的编写Java框架的的一个过程,主要描述我作为一个java初学者,在编写Java框架时的一些心得感悟. 因为我是C#的开发者,所以,在编写Java框架时,或多或少会带 ...

  2. linux软件deb打包及开机管理员自启动

    环境:Ubuntu 18.04/16.04  Qt:5.12.6 一 deb打包 1.建立目录结构 2.目录内容 1) 子目录DC520: Get以上内容步骤: (1)   创建目录DC520(自己软 ...

  3. P1223_排队接水(JAVA语言)

    思路 根据短作业优先平均等待时间最短的常识(默默感叹一句操作系统没白学),将Ti从小到大排序后,计算平均等待时间输出 //水题 题目描述 有n个人在一个水龙头前排队接水,假如每个人接水的时间为Ti,请 ...

  4. windows创建签名文件pfx

    https://stackoverflow.com/questions/84847/how-do-i-create-a-self-signed-certificate-for-code-signing ...

  5. C# 输出一个字符串的前缀、后缀和它的子串(信息内容安全 实验一)

    一.什么是前后缀 字符串的前缀:符号串左部的任意子串(或者说是字符串的任意首部) 字符串的后缀:符号串右部的任意子串(或者说是字符串的任意尾部) 举例:比如 101110 它的前缀就是空串.1.10. ...

  6. shiro报错SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".和Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory

    未能加载类"org.slf4j.impl.StaticLoggerBinder" 解决方案: <dependency> <groupId>org.slf4j ...

  7. 封装Vue纵向表头左右结构的table表格

    我们前端开发人员在使用表格的过程中,大概率碰到的都是表格头部在表格的最上边,然后呈一行展示,紧接着就是表格的每一行的每一个单元格来展示具体内容的场景,很少会遇到表格的头部呈纵向一行展示,也就是说表格的 ...

  8. 前端 | vxe-table 翻页保留复选框状态

    0 前言 在前端开发过程中时常会遇到表格相关的显示与处理.组件库通常都会提供表格组件,对于展示.简单操作这些常用功能通常也够用:但如果需要更多的定制或进行比较复杂的操作,组件库自带的组件可能会捉襟见肘 ...

  9. 你真的了解Innodb存储引擎?

    前言 前几篇记录了如何查看SQL执行计划.数据库事务相关的知识点 除了这两个,数据库还有两个是非常重要的,必须要考的 就是存储引擎和索引 今天先记录以下InnoDB存储引擎相关的知识点 MySQL存储 ...

  10. Go语言操作数据库及其常规操作

    Go操作MySQL 安装: go get -u github.com/go-sql-driver/mysql GO语言的操作数据库的驱动原生支持连接池, 并且是并发安全的 标准库没有具体的实现 只是列 ...