42. 接雨水
双指针方法
思路:
按照列来求,每一列的雨水量,是:自己做底h[i],左边比自己高的柱子left[i]和右边比自己高的柱子right[i]的最小值再减去底,因此为max(left[i],right[i])-h[i];
所以只需得到left和right数组;下面讲解left和right得到的逻辑。
left数组第一个元素初始化为h[0],因为第一个柱子左边没柱子,自己就是最高的,然后从左向右遍历,left[i]就是比左边一个柱子高的柱子和自己高度之间的最大值(逻辑是:如果左边柱子的left也就是left[i-1]比自己高,那么自己的left也就是left[i]就等于left[i-1];如果不比自己高,那么自己就是最高的,那么left[i]就是自己的高度height[i],因此取二者最大值即可);
right数组倒数第一个元素初始化为h[h.size()-1],因为右边第一个柱子的右边没柱子,自己就是最高的,然后从右向左遍历,right[i]就是比右边一个柱子高的柱子和自己高度之间的最大值(逻辑是:如果右边柱子的right也就是right[i+1]比自己高,那么自己的right也就是right[i]就等于right[i+1];如果不比自己高,那么自己就是最高的,那么right[i]就是自己的高度height[i],因此取二者最大值即可);
cpp
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
vector<int> maxLeft(n,0);
vector<int> maxRight(n,0);
maxLeft[0] = height[0];
maxRight[n-1] = height[n-1];
for(int i = 1;i<n;i++) maxLeft[i] = max(maxLeft[i-1],height[i]);
for(int j = n-2;j>=0;j--) maxRight[j] = max(maxRight[j+1],height[j]);
int result = 0;
for(int k =1;k<n-1;k++){
int water = min(maxLeft[k],maxRight[k])-height[k];
if(water>0) result+=water;
}
return result;
}
};
单调栈方法
思路:
重点是如何用单调栈获取左边的高柱和右边的高柱。其实单调递增栈,当当前元素比栈顶元素大的时候,就是"栈顶元素为底,当前元素为右边高柱,栈顶元素的下一个元素为左边高柱"的情形。
所以只需要在当前元素大于栈顶元素时,执行以下逻辑:
如果栈不为空,且当前元素大于栈顶元素:
获取栈顶元素为mid,把栈顶元素弹出;
再获取栈顶元素(左边高柱),取左右高柱最小值-h[mid],得到雨水深度,再乘以雨水宽度(i-栈顶元素(索引,也就是左边高柱的索引)-1);
(此处解释一下,为什么乘以宽度?这里不是按列求的了吗?每列宽度不就是1吗? 其实用单调栈就不是按列求的了,因为单调栈会在当前元素等于栈顶元素时,把当前元素也入栈,所以会出现栈内有多个连续相同的值,也就是非严格递增 。所以就相当于一个非严格抬升的台阶,在遇到严格抬升的地方的时候,前面可能已经有好几个连续没抬升的地方了,那些地方的储水量都是0,因为没抬升就没有储水高度,但当遍历到平地的最后一个元素(后面有一个严格高柱),它作为底时储水量为左右高柱最小值-h[mid],再乘以雨水宽度(平地宽度),其实也就相当于把这一片的水一次性加上了。所以这里其实是按行计算。
所以过程可以总结为:
柱子下降时入栈,柱子上升时出栈并计算储水量,遇到平柱(左高柱等于底高)不储水,遇到高柱,一次性储完前面平地的水。
cpp
class Solution {
public:
int trap(vector<int>& height) {
stack<int> st;
st.push(0);
int result = 0;
for(int i=1;i<height.size();i++){
if(height[i]<=height[st.top()]) st.push(i);
else{
while(!st.empty() && height[i]>height[st.top()]){
int mid = st.top();
st.pop();
if(!st.empty()){
result+=(min(height[i],height[st.top()])-height[mid])*(i-st.top()-1);
}
}
st.push(i);
}
}
return result;
}
};
84.柱状图中最大的矩形
思路:
目标是:对每一根柱子,找到它左右两边第一个比它矮的柱子,然后以当前柱子的高度作为矩形高度,计算它能扩展出的最大宽度;
所以本题使用递减单调栈,当遇到当前元素大于等于栈顶元素的情况执行入栈;
小于当前元素的时候在进行计算操作。
小于当前元素是,说明遇到了以栈顶元素为高度,当前元素为右矮柱,栈顶下一个元素为左矮柱的情况,此时可以切出一个高为栈顶元素,宽为左右矮柱索引之差-1的一个矩形;
为什么左右要补0,且不需要判断栈为空?
左补0,是为了避免栈弹空,导致元素漏算的情况,比如3,2,1,一开始3入栈,然后到2,又要把3弹出,此时栈就空了,但漏掉了1*3这个矩形;
右补0,是为了避免一直入栈,无法弹出,导致漏算,比如1,2,3,一直入栈,没遇到比栈顶元素小的元素,所以无法计算面积,所以漏掉了1*3这个矩形。
所以左右补0是为了补充左右边界,防止漏算的。
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& height) {
stack<int> st;
st.push(0);
int result = 0;
for(int i=1;i<height.size();i++){
if(height[i]>=height[st.top()]) st.push(i);
else{
while(!st.empty() && height[i]<height[st.top()]){
int mid = st.top();
st.pop();
if(!st.empty()){
result=max(min(height[i],height[st.top()])*(i-st.top()-1),result);
}
}
st.push(i);
}
}
return result;
}
};