单调栈
单调栈
-
经典用法:在数组中当前元素的两侧找第一个比当前元素更大(更小)的数在哪
-
维持求解答案的可能性
-
单调栈里的所有对象按照规定好的单调性来组织
-
当某个对象进入单调栈时,会从栈顶开始依次淘汰单调栈里对后续求解答案没有帮助的对象
-
每个对象从栈顶弹出时结算当前对象参与的答案,随后这个对象不再参与后续求解答案的过程
-
单调栈结构(进阶)
#include <iostream>
#include <vector>
#include <map>
#include <stack>
using namespace std;
// 给定一个可能含有重复值的数组 arr,找到每一个 i 位置左边和右边离 i 位置最近且值比 arr[i] 小的位置。
// 返回所有位置相应的信息。
int main() {
// 输入
int n;
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; ++i) cin >> arr[i];
// 记录下标 i 处的元素,两侧第一个比 arr[i] 小的元素位置
vector<pair<int, int>> res(n);
// 单调增栈,栈底到栈顶递增,记录元素下标
stack<int> stk;
// 遍历
for (int i = 0; i < n; ++i) {
// 栈不空且栈顶元素大于等于当前值时,弹出栈顶,结算两侧第一个更小的位置
while (!stk.empty() && arr[stk.top()] >= arr[i]) {
int popIndex = stk.top();
stk.pop();
// 左侧第一个更小为之前被压在元素下面的那个元素,也就是当前元素弹出后的新栈顶
res[popIndex].first = stk.empty() ? -1 : stk.top();
// 右侧第一个更小就是迫使他出栈的当前元素(arr 中有重复元素时,这个记录后续还需修正)
res[popIndex].second = i;
}
stk.emplace(i);
}
// 清算单调栈中剩余元素
while (!stk.empty()) {
int popIndex = stk.top();
stk.pop();
// 左侧第一个更小为之前被压在元素下面的那个元素,也就是当前元素弹出后的新栈顶
res[popIndex].first = stk.empty() ? -1 : stk.top();
// 右侧第一个更小不存在
res[popIndex].second = -1;
}
// 修正
for (int i = n - 2; i >= 0; i--)
// 右侧第一个更小存在且值和当前值相等时,修正为右侧的右侧
if (res[i].second != -1 && (arr[res[i].second] == arr[i]))
res[i].second = res[res[i].second].second;
// 输出
for (int i = 0; i < n; ++i)
cout << res[i].first << " " << res[i].second << endl;
}
739. 每日温度
#include <iostream>
#include <vector>
#include <map>
#include <stack>
using namespace std;
class Solution {
public:
// 右侧第一个更大
vector<int> dailyTemperatures(vector<int> &temperatures) {
vector<int> res(temperatures.size());
// 单调不增栈,从栈底到栈顶严格非递增
stack<int> stk;
for (int i = 0; i < temperatures.size(); ++i) {
// 栈不空且栈顶比当前元素小,栈顶出栈并记录右侧第一个更大
while (!stk.empty() && temperatures[stk.top()] < temperatures[i]) {
int popIndex = stk.top();
stk.pop();
// 右侧第一个更大就是当前元素
res[popIndex] = i - popIndex;
}
stk.emplace(i);
}
while (!stk.empty()) {
int popIndex = stk.top();
stk.pop();
// 不存在右侧第一个更大
res[popIndex] = 0;
}
return res;
}
};
907. 子数组的最小值之和
#include <iostream>
#include <vector>
#include <map>
#include <stack>
using namespace std;
class Solution {
public:
int sumSubarrayMins(vector<int> &arr) {
const int MOD = 1e9 + 7;
const int len = arr.size();
long res = 0;
// 栈底到栈顶递增
stack<int> stk;
// 遍历
for (int i = 0; i < len; ++i) {
// 每次栈顶出栈,都要结算左右第一个更小的位置
while (!stk.empty() && (arr[stk.top()] >= arr[i])) {
int popIndex = stk.top();
stk.pop();
int left = stk.empty() ? -1 : stk.top();
// arr[popIndex] 必须包含在连续子数组(l, r)内,l 为左侧第一个更小的,r 为右侧第一个更大的
// popIndex - left 为子数组开头位置的可能总数,i - popIndex 为结尾总数
long tempRes = (popIndex - left) * (i - popIndex) * (long) arr[popIndex];
res = (res + tempRes) % MOD;
}
stk.emplace(i);
}
// 清算栈中剩余
while (!stk.empty()) {
int popIndex = stk.top();
stk.pop();
int left = stk.empty() ? -1 : stk.top();
long tempRes = (popIndex - left) * (len - popIndex) * (long) arr[popIndex];
res = (res + tempRes) % MOD;
}
return (int) res;
}
};
84. 柱状图中最大的矩形
#include <iostream>
#include <vector>
#include <map>
#include <stack>
using namespace std;
class Solution {
public:
int largestRectangleArea(vector<int> &heights) {
int len = heights.size();
int res = 0;
// 栈底到栈顶递增
stack<int> stk;
// 以当前位置的高度为矩形的高度,向两侧扩展,直到两侧第一个更低的位置,构成矩形的宽
for (int i = 0; i < len; ++i) {
while (!stk.empty() && (heights[stk.top()] >= heights[i])) {
int popIndex = stk.top();
stk.pop();
int left = stk.empty() ? -1 : stk.top();
int width = i - left - 1;
res = max(res, heights[popIndex] * width);
}
stk.emplace(i);
}
while (!stk.empty()) {
int popIndex = stk.top();
stk.pop();
int left = stk.empty() ? -1 : stk.top();
int width = len - left - 1;
res = max(res, heights[popIndex] * width);
}
return res;
}
};
85. 最大矩形
#include <iostream>
#include <vector>
#include <map>
#include <stack>
using namespace std;
class Solution {
public:
int largestRectangleArea(vector<int> &heights) {
int len = heights.size();
int res = 0;
// 栈底到栈顶递增
stack<int> stk;
// 以当前位置的高度为矩形的高度,向两侧扩展,直到两侧第一个更低的位置,构成矩形的宽
for (int i = 0; i < len; ++i) {
while (!stk.empty() && (heights[stk.top()] >= heights[i])) {
int popIndex = stk.top();
stk.pop();
int left = stk.empty() ? -1 : stk.top();
int width = i - left - 1;
res = max(res, heights[popIndex] * width);
}
stk.emplace(i);
}
while (!stk.empty()) {
int popIndex = stk.top();
stk.pop();
int left = stk.empty() ? -1 : stk.top();
int width = len - left - 1;
res = max(res, heights[popIndex] * width);
}
return res;
}
int maximalRectangle(vector<vector<char>> &matrix) {
int res = 0;
int row = matrix.size();
int column = matrix.at(0).size();
// 压缩数组:记录当前行为底,往上数连续 1 的总数
vector<int> heights(column, 0);
for (int i = 0; i < row; ++i) {
for (int j = 0; j < column; ++j)
heights[j] = matrix[i][j] == '1' ? (heights[j] + 1) : 0;
// 计算以 i 行为底构成的最大矩形
res = max(res, largestRectangleArea(heights));
}
return res;
}
};
962. 最大宽度坡
#include <iostream>
#include <vector>
#include <map>
#include <stack>
using namespace std;
class Solution {
public:
int maxWidthRamp(vector<int> &nums) {
int res = 0;
// 栈底到栈顶递减
stack<int> stk;
for (int i = 0; i < nums.size(); ++i) {
// 比栈顶小才会进栈
if (stk.empty() || nums[stk.top()] > nums[i])
stk.emplace(i);
}
// 从右往左遍历,可以构造出最大宽度
for (int i = nums.size() - 1; i >= 0; i--) {
// 栈不空,且栈顶到当前元素能构成坡
while (!stk.empty() && nums[stk.top()] <= nums[i]) {
// 更新最大宽度
res = max(res, i - stk.top());
stk.pop();
}
}
return res;
}
};
316. 去除重复字母
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <stack>
using namespace std;
class Solution {
public:
string removeDuplicateLetters(string s) {
// 记录词频
unordered_map<char, int> freq;
// 记录有没有进栈
unordered_set<char> enter;
// 栈底到栈顶递增
stack<char> stk;
// 统计字符频率
for (auto& ch : s) freq[ch]++;
for (auto& ch : s) {
// 不在栈中才进行
if (enter.find(ch) == end(enter)) {
while (!stk.empty() && stk.top() > ch && freq[stk.top()] > 0) {
// 出栈
enter.erase(stk.top());
stk.pop();
}
// 进栈
stk.emplace(ch);
enter.insert(ch);
}
// 减少词频
freq[ch]--;
}
string res;
while (!stk.empty()) {
res.insert(0, 1, stk.top());
stk.pop();
}
return res;
}
};
大鱼吃小鱼
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
int main() {
int len;
cin >> len;
vector<int> arr(len);
for (int i = 0; i < len; ++i) cin >> arr[i];
// <鱼大小, 下标>,栈底到栈顶按照第一维递减
stack<pair<int, int>> stk;
int res = 0;
// 从右往左遍历
for (int i = len - 1; i >= 0; i--) {
int curTurns = 0;
while (!stk.empty() && stk.top().first < arr[i]) {
curTurns = max(curTurns + 1, stk.top().second);
stk.pop();
}
stk.emplace(make_pair(arr[i], curTurns));
res = max(res, curTurns);
}
cout << res;
}
1504. 统计全 1 子矩形
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
class Solution {
public:
// 比如
// 1
// 1
// 1 1
// 1 1 1
// 1 1 1
// 1 1 1
//
// 3 .... 6 .... 8
// left cur i
// 如上图,假设6位置从栈中弹出,6位置的高度为6(上面6个1)
// 6位置的左边、离6位置最近、且小于高度6的是3位置(left),3位置的高度是3
// 6位置的右边、离6位置最近、且小于高度6的是8位置(i),8位置的高度是4
// 此时我们求什么?
// 1) 求在4~7范围上必须以高度6作为高的矩形有几个?
// 2) 求在4~7范围上必须以高度5作为高的矩形有几个?
// 也就是说,<=4的高度一律不求,>6的高度一律不求!
// 其他位置也会从栈里弹出,等其他位置弹出的时候去求!
// 那么在4~7范围上必须以高度6作为高的矩形有几个?如下:
// 4..4 4..5 4..6 4..7
// 5..5 5..6 5..7
// 6..6 6..7
// 7..7
// 10个!什么公式?
// 4...7范围的长度为4,那么数量就是 : 4*5/2
// 同理在4~7范围上,必须以高度5作为高的矩形也是这么多
// 所以cur从栈里弹出时产生的数量 :
// (cur位置的高度-Max{left位置的高度,i位置的高度}) * ((i-left-1)*(i-left)/2)
int countFromBottom(stack<int> &stk, vector<int> &heights, int columns) {
int res = 0;
for (int i = 0; i < columns; ++i) {
while (!stk.empty() && heights[stk.top()] >= heights[i]) {
int cur = stk.top();
stk.pop();
if (heights[cur] > heights[i]) {
// 只有height[cur] > height[i]才结算
// 如果是因为height[cur]==height[i]导致cur位置从栈中弹出
// 那么不结算!等i位置弹出的时候再说!
int left = stk.empty() ? -1 : stk.top();
int len = i - left - 1;
int bottom = max(left == -1 ? 0 : heights[left], heights[i]);
res += (heights[cur] - bottom) * len * (len + 1) / 2;
}
}
stk.emplace(i);
}
while (!stk.empty()) {
int cur = stk.top();
stk.pop();
int left = stk.empty() ? -1 : stk.top();
int len = columns - left - 1;
int down = left == -1 ? 0 : heights[left];
res += (heights[cur] - down) * len * (len + 1) / 2;
}
return res;
}
int numSubmat(vector<vector<int>> &mat) {
int res = 0;
int rows = mat.size();
int columns = mat.at(0).size();
stack<int> stk;
vector<int> heights(columns, 0);
for (int i = 0; i < rows; ++i) {
// 压缩数组
for (int j = 0; j < columns; ++j)
heights[j] = mat[i][j] == 1 ? heights[j] + 1 : 0;
res += countFromBottom(stk, heights, columns);
}
return res;
}
};