文章目录
第十章 单调栈 part02
42. 接雨水
接雨水这道题目是面试中特别高频的一道题,也是单调栈应用的题目,大家好好做做。
建议是掌握双指针和单调栈,因为在面试中写出单调栈可能有点难度,但双指针思路更直接一些。
在时间紧张的情况下,能写出双指针法也是不错的,然后可以和面试官慢慢讨论如何优化。
示例数组:
height
=
[
0
,
1
,
0
,
2
,
1
,
0
,
1
,
3
,
2
,
1
,
2
,
1
]
\text{height} = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
height=[0,1,0,2,1,0,1,3,2,1,2,1]
过程解释表格:
i | height[i] | 栈内容 (st) | 栈中高度 (对应 height) | 弹出 mid 下标 | 左边界 (st.top()) | 右边界 (i) | 宽度 (w) | 高度 (h) | 当前累计水量 | 总水量 (sum) |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | [0] | [0] | - | - | - | - | - | - | 0 |
1 | 1 | [0, 1] | [0, 1] | - | - | - | - | - | - | 0 |
2 | 0 | [0, 1, 2] | [0, 1, 0] | - | - | - | - | - | - | 0 |
3 | 2 | [0, 1] | [0, 1] | 2 | 1 | 3 | 1 | 1 | 1 | 1 |
3 | 2 | [0] | [0] | 1 | 0 | 3 | 2 | 0 | 0 | 1 |
3 | 2 | [0, 3] | [0, 2] | - | - | - | - | - | - | 1 |
4 | 1 | [0, 3, 4] | [0, 2, 1] | - | - | - | - | - | - | 1 |
5 | 0 | [0, 3, 4, 5] | [0, 2, 1, 0] | - | - | - | - | - | - | 1 |
6 | 1 | [0, 3, 4] | [0, 2, 1] | 5 | 4 | 6 | 1 | 1 | 1 | 2 |
6 | 1 | [0, 3, 4, 6] | [0, 2, 1, 1] | - | - | - | - | - | - | 2 |
7 | 3 | [0, 3, 4] | [0, 2, 1] | 6 | 4 | 7 | 2 | 1 | 2 | 4 |
7 | 3 | [0, 3] | [0, 2] | 4 | 3 | 7 | 3 | 1 | 3 | 7 |
7 | 3 | [0] | [0] | 3 | 0 | 7 | 6 | 0 | 0 | 7 |
7 | 3 | [0, 7] | [0, 3] | - | - | - | - | - | - | 7 |
8 | 2 | [0, 7, 8] | [0, 3, 2] | - | - | - | - | - | - | 7 |
9 | 1 | [0, 7, 8, 9] | [0, 3, 2, 1] | - | - | - | - | - | - | 7 |
10 | 2 | [0, 7, 8] | [0, 3, 2] | 9 | 8 | 10 | 1 | 1 | 1 | 8 |
10 | 2 | [0, 7] | [0, 3] | 8 | 7 | 10 | 2 | 0 | 0 | 8 |
10 | 2 | [0, 7, 10] | [0, 3, 2] | - | - | - | - | - | - | 8 |
11 | 1 | [0, 7, 10, 11] | [0, 3, 2, 1] | - | - | - | - | - | - | 8 |
过程解析:
-
栈中高度的追踪:每当一个新柱子入栈时,我们不仅记录其索引,还可以追踪其对应的高度。这样可以很直观地看出栈中保存的每个柱子的高度是如何变化的。
-
形成凹槽时弹出的逻辑:每次当
height[i] > height[st.top()]
时,我们弹出栈顶元素(即mid
),并用栈中新的顶元素(st.top()
)作为左边界,当前元素i
作为右边界,计算出宽度和高度差,然后累加水量。 -
水量计算示例:
- 当
i = 3
时,弹出2
,左右边界分别是1
和3
,宽度为1
,高度差为1
,水量为1
。 - 当
i = 7
时,弹出多个柱子形成多个凹槽,水量分别累加。
- 当
这题确实挺难的,看了一遍视频后,再看文字,依然搞不懂。琢磨了半天。还是又看了一遍,才又懂了一点。确实对于不懂的东西,不要硬干,要老老实实的再看一遍。
stack<int> st;
int sum=0;
for(int i=0;i<height.size();i++)
{
while(!st.empty()&&height[i]>=height[st.top()])//发现有更高的右边界
{
int mid = st.top();//单调栈第一个拿来当作盛水的低
st.pop();//拿来用就给扔了,没用了
if (!st.empty()) {//看下单调栈是否为空,别是空的,保证左边能盛水
int h = min(height[st.top()], height[i]) - height[mid];//这是找左边最大值
int w = i - st.top() - 1; // 注意减一,只求中间宽度
sum += h * w;
}
}//注意while还在循环,因为右边多了一组墙,左边多了几组雨水
st.push(i);//把当前这个最大值扔进去,当作左边的墙
}
return sum;
}
部分内容看代码注释。
双指针法
class Solution {
public:
int trap(vector<int>& height) {
int sum=0;
vector<int> maxLeft(height.size(), 0);
vector<int> maxRight(height.size(), 0);
for(int i=1;i<height.size()-1;i++)
{
maxLeft[i]=max(maxLeft[i-1],height[i-1]);
}
for(int i=height.size()-2;i>0;i--)
{
maxRight[i]=max(maxRight[i+1],height[i+1]);
}
for(int i=1;i<height.size()-1;i++)
{
sum+=max(min(maxLeft[i],maxRight[i])-height[i],0);
}
return sum;
}
};
双指针法还是比较好理解的。稍微用点动态规划的简单知识。记录每个柱子左边柱子最大高度,记录每个柱子右边柱子最大高度,求和。三个for循环。简简单单。
84. 柱状图中最大的矩形
有了之前单调栈的铺垫,这道题目就不难了。
双指针法
class Solution {
public:
int largestRectangleArea(vector<int>& height) {
int result=0;
int n=height.size();
vector<int> minLeft(n, 0);
vector<int> minRight(n, 0);
minLeft[0] = -1;
for(int i=1;i<n;i++)
{
int t = i - 1;
while (t >= 0 && height[t] >= height[i]) t = minLeft[t];
minLeft[i] = t;
}
minRight[n - 1] = n;
for(int i=n-2;i>=0;i--)
{
int t = i + 1;
// 这里不是用if,而是不断向右寻找的过程
while (t < n && height[t] >= height[i]) t = minRight[t];
minRight[i] = t;
}
for(int i=0;i<n;i++)
{
int sum = height[i] * (minRight[i] - minLeft[i] - 1);
result = max(sum, result);
}
return result;
}
};
双指针法还是比较简单的。最重要的是知道循环找右边的思路
单调栈法
传送门:0084.柱状图中最大的矩形
在 height数组上后,都加了一个元素0, 为什么这么做呢?
首先来说末尾为什么要加元素0?
如果数组本身就是升序的,例如[2,4,6,8],那么入栈之后 都是单调递减,一直都没有走 情况三 计算结果的哪一步,所以最后输出的就是0了。
那么结尾加一个0,就会让栈里的所有元素,走到情况三的逻辑。
开头为什么要加元素0?
如果数组本身是降序的,例如 [8,6,4,2],在 8 入栈后,6 开始与8 进行比较,此时我们得到 mid(8),right(6),但是得不到 left。
(mid、left,right 都是对应版本一里的逻辑)
因为 将 8 弹出之后,栈里没有元素了,那么为了避免空栈取值,直接跳过了计算结果的逻辑。
之后又将6 加入栈(此时8已经弹出了),然后 就是 4 与 栈口元素 6 进行比较,周而复始,那么计算的最后结果result就是0。
最后发现,单调栈的思路有点像砍山头。逐渐把前面的山头给砍平。看懂下面的过程,差不多
1 | 3 | 5 | 2 | 1 | 3 | 1 | 面积 |
---|---|---|---|---|---|---|---|
1 | 3 | 5 | 2 | ||||
1 | 3 | 5 | 2 | 5 | |||
1 | 3 | 比前大 | 2 | 6 | |||
1 | 比前大 | 比前大 | 2 | 3 | |||
1 | 比前大 | 比前大 | 2 | 1 | 2 | ||
1 | 比前大 | 比前大 | 2 | 1 | 2 | ||
1 | 比前大 | 比前大 | 比前大 | 1 | 3 | ||
1 | 比前大 | 比前大 | 比前大 | 1 | 3 | 1 | 3 |
1 | 比前大 | 比前大 | 比前大 | 1 | 比前大 | 1 | 6 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 7 |
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int> st;
st.push(0);
heights.insert(heights.begin(), 0); // 数组头部加入元素0
heights.push_back(0); // 数组尾部加入元素0
int result = 0;
int n=heights.size();
for(int i=1;i<n;i++)
{
while (heights[i] < heights[st.top()]) {
int mid = st.top();//最顶就是st.top()
st.pop();//用了就扔了,不用再管他了,它已经到极限了
int w = i - st.top() - 1;//确定宽
int h = heights[mid];//确定高
result = max(result, w * h);//比较是否最大
}//注意一直循环,只有有更大的就削山头。直到单调栈。
st.push(i);//加入进去,准备下一个
}
return result;
}
};
终于至少把《代码随想录》这本书刷完了。自己效率确实挺低的,两个多月才刷到这。图论不打算刷了,一方面卡玛网不太喜欢,一方面前面够我消化的了,还是不太想刷图论了。还是很有收获。确实自己速度很慢。但也已经尽力了。最重要的还是感觉自己没有什么动力,感觉自己刷题更多的是为了逃避现实吧。反而没有经历搞自己的学业。难搞,反正先暂停一段时间,搞搞课题。
标签:int,随想录,top,42,st,part2,------,height,单调 From: https://blog.csdn.net/zxjiaya/article/details/142936795