问题引入:
找出一段序列的每个数的右边距离该数最近的最大值。
朴素思路:
普通的做法两层循环(类似于双指针算法,找到了就break),但是这样写的话效率就很低了。
模拟过程:
现给出数组:1 4 3 5 5 2 3 6
假设从右往左思考,当已经放入2 3 6时,下一个数是5,只要5存在,就一定用不到 2 3 了。这个时候当前维护的数组就可以从 5 2 3 6 变为5 6了。后面的过程同理。
对于这样的一个结构:首先我们可以发现:后进入的数会最先出去,先进入的数最后出去,这是栈的结构。同时也要满足栈内元素是单调的:即要么一直增大要么一直减小。
这就是我们所说的单调栈了。
单调栈的套路:
一般用单调栈都是求数组中某个元素,离这个元素最近的左边(上一个)/右边(下一个)的最大值/最小值。这里就要考虑道一个问题:数组遍历的问题。有时候从前往后遍历,有时候从后往前遍历。
这里给出一个经验写法:如果求的是该元素最近的左边(上一个)的最值:就从前往后循环,将栈底向左,栈顶向右。如果求的是该元素最近的右边(下一个)的最值:就从后往前循环,将栈底向右,栈顶向左。
模板:
for (int i = 0; i < vec.size(); i++)
{
while (st.size() && vec[st.top()] >= vec[i]) //非空且需要弹出时
st.pop();
// 具体逻辑
if (st.size()) cout << "左边距离" << vec[i] << "最近的最小值是" << vec[st.top()] << endl;
else cout << "左边没有比" << vec[i] << "更小的数" << endl;
st.push(i); // 弹入
}
两道例题:
由于是下一个更大元素,也就是求当前元素的右边的最大值,所以我们遍历肯定是从后往前的。
这里还要注意:循环数组的话我们可以做一些初始化操作:再复制一遍数组加到尾上就行了。但是填充数据的时候还是要求i<n才可以填充。
class Solution {
public:
stack<int> st;
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> vec(n,-1);
for(int i = 0;i<n;i++)
nums.push_back(nums[i]);
int m = nums.size();
for(int i = m-1;i>=0;i--)
{
while(st.size() && nums[st.top()]<=nums[i])
st.pop();
if(st.size() && i<n)
vec[i] = nums[st.top()];
st.push(i);
}
return vec;
}
};
这题难度大一些:如果我们一个一个去枚举子集然后去更新最小值,是O(n^2)的复杂度。那我们就换个角度去思考:思考当前这个数对答案有多少贡献,然后遍历一边数组就可以了。这样的复杂度就是O(n)的。
假设现在数组是 arr = {1,4,2,3,1},其中2是最小值的子集有{2},{2,3},{4,2},{4,2,3},所以2的贡献值是2*4 = 8.
2如果要满足在当前子集中最小,则向左向右扩展时不能遇到1,下标满足为[1,3](闭区间),也可以写成(0,4)(闭区间)。
记 L = 0,R = 4,当前下标为i。则a[i]的贡献为: a[i] * (R-i)*(i-L).
若arr=[1,2,4,2,3,1]呢?这样对2计算就会有重复。我们把右边界修改为小于等于a[i]的下标就可以了。 第一个2:(0,3),第二个2:(0,5)
现在的问题就是如何计算出对于a[i]的上一个严格小于a[i]元素位置,和下一个小于等于a[i]元素位置了。
使用两遍单调栈即可。
注意: 在计算贡献的时候,如果本身也算进去的话,下标从-1开始,到n结束。
class Solution {
public:
const int MOD = 1e9+7;
int sumSubarrayMins(vector<int>& arr) {
int n = arr.size();
vector<int> left(n,-1);
stack<int> st;
for(int i = 0;i<n;i++)
{
while(!st.empty() && arr[st.top()] >= arr[i])
st.pop();
if(!st.empty()) left[i] = st.top();
st.push(i);
}
vector<int> right(n,n);
while(!st.empty()) st.pop();
for (int i = n - 1; i >= 0; --i) {
while (!st.empty() && arr[st.top()] > arr[i])
st.pop();
if (!st.empty()) right[i] = st.top();
st.push(i);
}
long ans = 0L;
for (int i = 0; i < n; ++i)
ans += (long) arr[i] * (i - left[i]) * (right[i] - i); // 累加贡献
return ans % MOD;
}
};