更新图床

This commit is contained in:
programmercarl
2023-03-10 14:02:32 +08:00
parent 2a9b627a90
commit 17cb4b45c7
134 changed files with 1169 additions and 829 deletions

View File

@@ -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
![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210713205038.png)
![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210713205038.png)
* 输入height = [0,1,0,2,1,0,1,3,2,1,2,1]
* 输出6
@@ -27,26 +29,27 @@
* 输出9
# 思路
# 思路
接雨水问题在面试中还是常见题目的,有必要好好讲一讲。
本文深度讲解如下三种方法:
* 双指针法
* 动态规划
* 单调栈
## 暴力解法
## 暴力解法
本题暴力解法也是也是使用双指针。
首先要明确,要按照行来计算,还是按照列来计算。
按照行来计算如图:
![42.接雨水2](https://img-blog.csdnimg.cn/20210402091118927.png)
![42.接雨水2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210402091118927.png)
按照列来计算如图:
![42.接雨水1](https://img-blog.csdnimg.cn/20210402091208445.png)
![42.接雨水1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210402091208445.png)
一些同学在实现的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
@@ -58,7 +61,7 @@
这句话可以有点绕来举一个理解例如求列4的雨水高度如图
![42.接雨水3](https://img-blog.csdnimg.cn/20210223092732301.png)
![42.接雨水3](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223092732301.png)
列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. 首先单调栈是按照行方向来计算雨水,如图:
![42.接雨水2](https://img-blog.csdnimg.cn/20210223092629946.png)
![42.接雨水2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223092629946.png)
知道这一点,后面的就可以理解了。
@@ -211,11 +214,11 @@ public:
如图:
![42.接雨水4](https://img-blog.csdnimg.cn/2021022309321229.png)
![42.接雨水4](https://code-thinking-1253855093.file.myqcloud.com/pics/2021022309321229.png)
关于单调栈的顺序给大家一个总结: [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 中求一个元素右边第一个更大元素,单调栈就是递增的,[84.柱状图中最大的矩形](https://programmercarl.com/0084.柱状图中最大的矩形.html)求一个元素右边第一个更小元素,单调栈就是递减的。
3. 遇到相同高度的柱子怎么办。
3. 遇到相同高度的柱子怎么办。
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
@@ -225,7 +228,7 @@ public:
如图所示:
![42.接雨水5](https://img-blog.csdnimg.cn/20210223094619398.png)
![42.接雨水5](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223094619398.png)
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 这种情况
}
```
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
![42.接雨水4](https://img-blog.csdnimg.cn/2021022309321229.png)
![42.接雨水4](https://code-thinking-1253855093.file.myqcloud.com/pics/2021022309321229-20230310123027977.png)
取栈顶元素将栈顶元素弹出这个就是凹槽的底部也就是中间位置下标记为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;