更新图床
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
|
||||
<img src="../pics/训练营.png" width="1000"/>
|
||||
@@ -5,9 +6,10 @@
|
||||
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
|
||||
|
||||
|
||||
|
||||
> 这个图就是大厂面试经典题目,接雨水! 最常青藤的一道题,面试官百出不厌!
|
||||
|
||||
# 42. 接雨水
|
||||
# 42. 接雨水
|
||||
|
||||
[力扣题目链接](https://leetcode.cn/problems/trapping-rain-water/)
|
||||
|
||||
@@ -15,7 +17,7 @@
|
||||
|
||||
示例 1:
|
||||
|
||||

|
||||

|
||||
|
||||
* 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
|
||||
* 输出:6
|
||||
@@ -27,26 +29,27 @@
|
||||
* 输出:9
|
||||
|
||||
|
||||
# 思路
|
||||
# 思路
|
||||
|
||||
接雨水问题在面试中还是常见题目的,有必要好好讲一讲。
|
||||
|
||||
本文深度讲解如下三种方法:
|
||||
|
||||
* 双指针法
|
||||
* 动态规划
|
||||
* 单调栈
|
||||
|
||||
## 暴力解法
|
||||
## 暴力解法
|
||||
|
||||
本题暴力解法也是也是使用双指针。
|
||||
|
||||
首先要明确,要按照行来计算,还是按照列来计算。
|
||||
|
||||
按照行来计算如图:
|
||||

|
||||

|
||||
|
||||
按照列来计算如图:
|
||||

|
||||

|
||||
|
||||
一些同学在实现的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
|
||||
|
||||
@@ -58,7 +61,7 @@
|
||||
|
||||
这句话可以有点绕,来举一个理解,例如求列4的雨水高度,如图:
|
||||
|
||||

|
||||

|
||||
|
||||
列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。
|
||||
|
||||
@@ -72,7 +75,7 @@
|
||||
|
||||
此时求出了列4的雨水体积。
|
||||
|
||||
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
|
||||
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
|
||||
|
||||
首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:
|
||||
|
||||
@@ -132,7 +135,7 @@ public:
|
||||
|
||||
因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)。
|
||||
|
||||
力扣后面修改了后台测试数据,所以以上暴力解法超时了。
|
||||
力扣后面修改了后台测试数据,所以以上暴力解法超时了。
|
||||
|
||||
## 双指针优化
|
||||
|
||||
@@ -181,9 +184,9 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
## 单调栈解法
|
||||
## 单调栈解法
|
||||
|
||||
关于单调栈的理论基础,单调栈适合解决什么问题,单调栈的工作过程,大家可以先看这题讲解 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。
|
||||
关于单调栈的理论基础,单调栈适合解决什么问题,单调栈的工作过程,大家可以先看这题讲解 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。
|
||||
|
||||
单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://programmercarl.com/0239.滑动窗口最大值.html)一样,需要我们自己维持顺序,没有现成的容器可以用。
|
||||
|
||||
@@ -197,7 +200,7 @@ public:
|
||||
|
||||
1. 首先单调栈是按照行方向来计算雨水,如图:
|
||||
|
||||

|
||||

|
||||
|
||||
知道这一点,后面的就可以理解了。
|
||||
|
||||
@@ -211,11 +214,11 @@ public:
|
||||
|
||||
如图:
|
||||
|
||||

|
||||

|
||||
|
||||
关于单调栈的顺序给大家一个总结: [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 中求一个元素右边第一个更大元素,单调栈就是递增的,[84.柱状图中最大的矩形](https://programmercarl.com/0084.柱状图中最大的矩形.html)求一个元素右边第一个更小元素,单调栈就是递减的。
|
||||
|
||||
3. 遇到相同高度的柱子怎么办。
|
||||
3. 遇到相同高度的柱子怎么办。
|
||||
|
||||
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
|
||||
|
||||
@@ -225,7 +228,7 @@ public:
|
||||
|
||||
如图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
4. 栈里要保存什么数值
|
||||
|
||||
@@ -233,7 +236,7 @@ public:
|
||||
|
||||
长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,
|
||||
|
||||
那么栈里有没有必要存一个pair<int, int>类型的元素,保存柱子的高度和下标呢。
|
||||
那么栈里有没有必要存一个pair<int, int>类型的元素,保存柱子的高度和下标呢。
|
||||
|
||||
其实不用,栈里就存放下标就行,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了。
|
||||
|
||||
@@ -245,17 +248,17 @@ stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度
|
||||
|
||||
明确了如上几点,我们再来看处理逻辑。
|
||||
|
||||
### 单调栈处理逻辑
|
||||
### 单调栈处理逻辑
|
||||
|
||||
以下操作过程其实和 [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 也是一样的,建议先做 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。
|
||||
以下操作过程其实和 [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 也是一样的,建议先做 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。
|
||||
|
||||
以下逻辑主要就是三种情况
|
||||
以下逻辑主要就是三种情况
|
||||
|
||||
* 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
|
||||
* 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
|
||||
* 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]
|
||||
* 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
|
||||
* 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
|
||||
* 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]
|
||||
|
||||
先将下标0的柱子加入到栈中,`st.push(0);`。 栈中存放我们遍历过的元素,所以先将下标0加进来。
|
||||
先将下标0的柱子加入到栈中,`st.push(0);`。 栈中存放我们遍历过的元素,所以先将下标0加进来。
|
||||
|
||||
然后开始从下标1开始遍历所有的柱子,`for (int i = 1; i < height.size(); i++)`。
|
||||
|
||||
@@ -278,9 +281,9 @@ if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
|
||||
}
|
||||
```
|
||||
|
||||
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
|
||||
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
取栈顶元素,将栈顶元素弹出,这个就是凹槽的底部,也就是中间位置,下标记为mid,对应的高度为height[mid](就是图中的高度1)。
|
||||
|
||||
@@ -290,7 +293,7 @@ if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
|
||||
|
||||
此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!**
|
||||
|
||||
那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];`
|
||||
那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];`
|
||||
|
||||
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:`int w = i - st.top() - 1 ;`
|
||||
|
||||
@@ -373,11 +376,12 @@ public:
|
||||
精简之后的代码,大家就看不出去三种情况的处理了,貌似好像只处理的情况三,其实是把情况一和情况二融合了。 这样的代码不太利于理解。
|
||||
|
||||
|
||||
## 其他语言版本
|
||||
## 其他语言版本
|
||||
|
||||
### Java:
|
||||
### Java:
|
||||
|
||||
暴力解法:
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int trap(int[] height) {
|
||||
@@ -385,7 +389,7 @@ class Solution {
|
||||
for (int i = 0; i < height.length; i++) {
|
||||
// 第一个柱子和最后一个柱子不接雨水
|
||||
if (i==0 || i== height.length - 1) continue;
|
||||
|
||||
|
||||
int rHeight = height[i]; // 记录右边柱子的最高高度
|
||||
int lHeight = height[i]; // 记录左边柱子的最高高度
|
||||
for (int r = i+1; r < height.length; r++) {
|
||||
@@ -404,6 +408,7 @@ class Solution {
|
||||
```
|
||||
|
||||
双指针:
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int trap(int[] height) {
|
||||
@@ -411,15 +416,15 @@ class Solution {
|
||||
if (length <= 2) return 0;
|
||||
int[] maxLeft = new int[length];
|
||||
int[] maxRight = new int[length];
|
||||
|
||||
|
||||
// 记录每个柱子左边柱子最大高度
|
||||
maxLeft[0] = height[0];
|
||||
for (int i = 1; i< length; i++) maxLeft[i] = Math.max(height[i], maxLeft[i-1]);
|
||||
|
||||
|
||||
// 记录每个柱子右边柱子最大高度
|
||||
maxRight[length - 1] = height[length - 1];
|
||||
for(int i = length - 2; i >= 0; i--) maxRight[i] = Math.max(height[i], maxRight[i+1]);
|
||||
|
||||
|
||||
// 求和
|
||||
int sum = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
@@ -432,13 +437,14 @@ class Solution {
|
||||
```
|
||||
|
||||
单调栈法
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int trap(int[] height){
|
||||
int size = height.length;
|
||||
|
||||
if (size <= 2) return 0;
|
||||
|
||||
|
||||
// in the stack, we push the index of array
|
||||
// using height[] to access the real height
|
||||
Stack<Integer> stack = new Stack<Integer>();
|
||||
@@ -458,7 +464,7 @@ class Solution {
|
||||
int heightAtIdx = height[index];
|
||||
while (!stack.isEmpty() && (heightAtIdx > height[stackTop])){
|
||||
int mid = stack.pop();
|
||||
|
||||
|
||||
if (!stack.isEmpty()){
|
||||
int left = stack.peek();
|
||||
|
||||
@@ -472,7 +478,7 @@ class Solution {
|
||||
stack.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
@@ -481,6 +487,7 @@ class Solution {
|
||||
### Python:
|
||||
|
||||
暴力解法:
|
||||
|
||||
```Python
|
||||
class Solution:
|
||||
def trap(self, height: List[int]) -> int:
|
||||
@@ -495,32 +502,35 @@ class Solution:
|
||||
for k in range(i+2,len(height)):
|
||||
if height[k] > rHight:
|
||||
rHight = height[k]
|
||||
res1 = min(lHight,rHight) - height[i]
|
||||
res1 = min(lHight,rHight) - height[i]
|
||||
if res1 > 0:
|
||||
res += res1
|
||||
return res
|
||||
```
|
||||
|
||||
双指针:
|
||||
|
||||
```python
|
||||
class Solution:
|
||||
def trap(self, height: List[int]) -> int:
|
||||
leftheight, rightheight = [0]*len(height), [0]*len(height)
|
||||
|
||||
|
||||
leftheight[0]=height[0]
|
||||
for i in range(1,len(height)):
|
||||
leftheight[i]=max(leftheight[i-1],height[i])
|
||||
rightheight[-1]=height[-1]
|
||||
for i in range(len(height)-2,-1,-1):
|
||||
rightheight[i]=max(rightheight[i+1],height[i])
|
||||
|
||||
|
||||
result = 0
|
||||
for i in range(0,len(height)):
|
||||
summ = min(leftheight[i],rightheight[i])-height[i]
|
||||
result += summ
|
||||
return result
|
||||
```
|
||||
|
||||
单调栈
|
||||
|
||||
```Python
|
||||
class Solution:
|
||||
def trap(self, height: List[int]) -> int:
|
||||
@@ -565,8 +575,8 @@ class Solution:
|
||||
result += h * w
|
||||
stack.append(i)
|
||||
return result
|
||||
|
||||
# 单调栈压缩版
|
||||
|
||||
# 单调栈压缩版
|
||||
class Solution:
|
||||
def trap(self, height: List[int]) -> int:
|
||||
stack = [0]
|
||||
@@ -586,7 +596,7 @@ class Solution:
|
||||
|
||||
```
|
||||
|
||||
### Go
|
||||
### Go
|
||||
|
||||
```go
|
||||
func trap(height []int) int {
|
||||
@@ -601,7 +611,7 @@ func trap(height []int) int {
|
||||
}
|
||||
left++
|
||||
} else {
|
||||
if height[right] > rightMax {
|
||||
if height[right] > rightMax {
|
||||
rightMax = height[right] // //设置右边最高柱子
|
||||
} else {
|
||||
res += rightMax - height[right] // //左边必定有柱子挡水,所以,遇到所有值小于等于rightMax的,全部加入水池
|
||||
@@ -652,6 +662,7 @@ func min(a,b int)int{
|
||||
```
|
||||
|
||||
单调栈解法
|
||||
|
||||
```go
|
||||
func trap(height []int) int {
|
||||
if len(height) <= 2 {
|
||||
@@ -896,12 +907,12 @@ int trap(int* height, int heightSize) {
|
||||
while (left < right) { //两个指针重合就结束
|
||||
leftMax = fmax(leftMax, height[left]);
|
||||
rightMax = fmax(rightMax, height[right]);
|
||||
if (leftMax < rightMax) {
|
||||
if (leftMax < rightMax) {
|
||||
ans += leftMax - height[left]; //这里考虑的是下标为left的“底”能装多少水
|
||||
++left;//指针的移动次序是这个方法的关键
|
||||
//这里左指针右移是因为左“墙”较矮,左边这一片实际情况下的盛水量是受制于这个矮的左“墙”的
|
||||
//而较高的右边在实际情况下的限制条件可能不是当前的左“墙”,比如限制条件可能是右“墙”,就能装更高的水,
|
||||
}
|
||||
}
|
||||
else {
|
||||
ans += rightMax - height[right]; //同理,考虑下标为right的元素
|
||||
--right;
|
||||
|
||||
Reference in New Issue
Block a user