开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
单调栈
-
定义:单调栈,栈底到栈顶,按照单调递增或者单调递减的顺序。
-
应用场景
面对一个序列,经常可以被用来应用求:
-
左边距离它最近且比它大/小的数字和右边距离它最近的且比他大/小的数字
-
当前元素的左边最大/最小的元素。
比如单调减增栈。对于某个元素,它下面的数就是他左边距离最近的比他大的数字,让它弹出就是它右边距离最近的比大的数字。且在每次弹出时开始计算有关题目要求的。
-
末尾注意 在遍历所有元素结束的时候,如果栈中还有元素,要依次弹出结算。
比如,存在数组
[3,5,4,7,1],保持栈是单独递减。1)首先将3入栈,此时栈是空的,可以直接入栈 1) 当将5入栈时,5比3大,此时就可以计算3的右侧距离最近的比他大的元素,这个元素就是5.因为3左侧没有那么元素,那么就是NULL。将3弹出后,再将5入栈 2) 4比5小,可以直接入栈, 4)7大于4,因此此时要计算4的左右两侧距离最近比他大的元素。左侧是5,右侧是让他弹出的元素7 1) 同理,也要弹出5,左侧是null,右侧是7 6)入栈7 7)入栈1 8)此时序列已经遍历结束,要弹出1,让1弹出的因为遍历结束,右侧是null.左侧7 9)让7弹出的是null,左侧也是null 顺便也知道了,左右两侧都是null的元素就是最大值。如果题目有限定条件,比如不会出现
INT_MAX,那么也是可以在原数组后面nums.push_back(INT_MAX),这样就不用再单独讨论序列遍及结束时候的问题了。
-
Leetcode
最大二叉树
假设一个输入序列:[3, 2, 1, 6, 0, 5]
设置一个辅助栈,从大到小存储,保持单调递减。
1) 首先入栈3
2)2 比 3 小,入栈
3)1 比 2 小,入栈
4)6 大于1,因此要弹出1,1在2和6之间选择二者之间较小的元素作为父节点,因此选择2。1在2的右侧,使得1作为2的右子节点
5)弹出1后,6仍然比2大,同理2要在3和6之间选择一个作为父节点。3比6小,因此选择3。2在3的右侧,因此2作为3的右子节点
6)同理弹出3,让3作为6的左子节点
7)入栈6
8)入栈0
9)入栈5的时候比0大,要弹出0,选择5作为父节点,并且0是5的左孩子
10)弹出5,左侧是6,作为5的父节点
11)6最后弹出,就是根节点
class Solution {
public:
TreeNode* constructMaximumBinaryTree(std::vector<int>& nums) {
std::stack<TreeNode*> nodes;
TreeNode* curNode = nullptr;
for(size_t i=0; i < nums.size(); ++i) {
curNode = new TreeNode(nums[i]);
while(!nodes.empty() && nodes.top()->val < curNode->val) {
TreeNode* top = nodes.top();
nodes.pop();
if(!nodes.empty() && nodes.top()->val < curNode->val)
{
nodes.top()->right = top;
}
else
{
curNode->left = top;
}
}
nodes.push(curNode);
}
// 遍历结束,此时栈中可能还是会有一些元素
while(!nodes.empty()) {
curNode = nodes.top();
nodes.pop();
if(!nodes.empty())
nodes.top()->right = curNode;
}
return curNode;
}
};
直方图
求的是最大的矩形面积。其实就是求一个矩阵可以向左向左移动的获得的面积最大值 max {L_i*area_i}。最大值受限于一个柱子左右两边距离最近的比他小的柱子。 符合单调栈的应用场景。
示例[2,1,5,6,2,3]。
栈的单调性。单调递增栈,遇到小的就弹出:
1)空栈,2的下标直接入栈
1) 1比2小,此时要弹出2,并计算2能移动的面积:(1- (-1)+1) * 2 = 2
2) 1入栈
4)5 比 1大,5下标入栈
1) 6 比5大,6下标直接入栈
2) 2比6小,此时可以计算6的移动面积:(4-2+1) * 6 = 6
3) 2还比5小,此时计算5的移动面积:(4-1+1)*5 = 10
4) 2 比1大,直接入栈
9)3比2大,直接入栈
此时遍历完毕,但是栈中还是有3个元素。
10)先是弹出3,并计算3的移动面积:3右侧序列的最大长度:(6-4-1)*3 = 3
11)同理,弹出2,计算面积 (6-3-1)*2 = 4
12) 同理弹出1,计算面积:(6- (-1) -1)*1 = 6
此时最大是10.
因此,有代码实现:
class Solution {
public:
int largestRectangleArea(const std::vector<int>& heights) {
std::stack<int> path;
int maxArea =0;
int N = heights.size();
for(int i=0; i < N; ++i) {
while(!path.empty() && heights[path.top()] > heights[i]) {
int curr = path.top(); path.pop();
int left = path.empty() ? -1 : path.top();
maxArea= std::max(maxArea, (i - left -1) * heights[curr]);
}
path.push(i);
}
while(!path.empty()) {
int curr = path.top(); path.pop();
int left = path.empty() ? -1 : path.top();
maxArea= std::max(maxArea, (N - left -1) * heights[curr]);
}
return maxArea;
}
};
最大子矩阵
这道题目是84的题目的一个升级版,将每一行都看作是一个直方图,直接遍历即可。
class Solution {
public:
int maximalRectangle(std::vector<std::vector<char>>& matrix) {
if(matrix.empty()) return 0;
int cols = matrix[0].size();
std::vector<int> line(cols);
int maxArea =0;
for(const auto& base : matrix) {
for(int col=0; col < cols; ++col) {
line[col] = base[col] !='0' ? line[col]+1 : 0;
}
maxArea = std::max(maxArea, largestRectangleArea(line)); // 上一题的函数
}
return maxArea;
}
};
接雨水
对于输入序列为 [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] ,如下如图所示。这个题目与直方图所求的最大面积不同,求的是 “面积和”。根据木桶理论,水位限制于低的木板,因此维护一个单调递增的栈,当出现当前木板高度小于栈顶的木板高度,那么就可以计算栈顶木板能存储的水面积了。
class Solution {
public:
int trap(vector<int>& height) {
if(height.empty()) return 0;
std::stack<int> path;
int sum = 0;
for(int curr = 0; curr < height.size(); ++curr){
// 栈顶木板高度 小于 当前木板高度
while(!path.empty() && height[path.top()] < height[curr]) {
int h = height[path.top()]; path.pop(); // 栈顶高度
if(path.empty()) break;
int distance = curr - path.top() -1;
int min = std::min(height[path.top()], height[curr]);
sum += distance * (min - h); // 这里还有个减法,因为要从当前水位开始计算
}
path.push(curr);
}
return sum;
}
};
买卖股票的最佳时机 I
这道题,直观上求的就是对于每个元素其比他大的右边的最大值是多少。单调栈的解法,巧妙的转换了思路:求的是对于当前元素其左边最小的元素。保持栈的单调递减属性,那么每次对于当前元素而言,其左边最小的元素就是栈底。
因此,对于当前数:curr - base
class Solution {
public:
int maxProfit(std::vector<int>& prices) {
std::stack<int> path;
int base =0; // 栈底
int maxProfit = 0;
for(int i=0; i < prices.size(); ++i) {
// 栈顶的值 > 当前值,那么可以计算最大利润了
while(!path.empty() && prices[path.top()] > prices[i]) {
int top = path.top(); path.pop(); // 栈顶
// 栈中的元素肯定是保持单调递增的
int profit = prices[top] - prices[base];
maxProfit = std::max(profit, maxProfit);
}
// 当前元素 prices[i] 比栈中任何一个元素都小时,就会使得栈空
if(path.empty()) { base =i; }
path.push(i);
}
while(!path.empty()) {
int top = path.top();
path.pop();
int profit = prices[top] - prices[base];
maxProfit = std::max(profit, maxProfit);
}
return maxProfit;
}
};