题目
907. 子数组的最小值之和
题面
给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。
由于答案可能很大,因此 返回答案模 10^9 + 7 。
关键词
单调栈
思路
**第一个问题:**考虑数组arr中的每一个值arr[i]作为最小值,能够包含在哪些子数组中,并求出这些子数组的个数
方法:
需要找到arr[i]的左边界left和右边界right,满足:
① arr[left]……arr[i-1]都大于等于arr[i]
② arr[i+1]……arr[right]都大于arr[i]
(这里一个大于等于,一个大于,目的是为了避免子数组的重复计算,最后解释)
此时left……i都可以作为arr[i]为最小值的子数组的左边界,i……right都可以作为arr[i]为最小值的子数组的右边界
比如[3,1,2,4],对于1来说,3,1都可以作为1的左边界,1,2,4都可以作为1的右边界,
此时以1为最小值的所有子数组为[3,1],[3,1,2],[3,1,2,4],[1,1],[1,2],[1,2,4],共6个子数组,
故可知对arr[i],若其满足上述条件的左右边界为left和right,则子数组的个数为(i-left+1)*(right-i+1)
**第二个问题:**怎么求左右边界
方法:
首先暴力求解是会超时的,所以我们需要考虑left和right是如何求得的
对于left,是满足上述条件的大于等于arr[i]的最左边界
对于right,是满足上述条件的大于arr[i]的最右边界
所以可以考虑维护一个单调递增的单调栈,用于找某个值沿某一个方向上的下一个更小的值。
(单调栈从字面意思上就是栈中元素始终保持单调性)
举例说明:
数组 [3,1,2,1],单调栈初始空,这里用”【 “表示栈底
(下面解释中栈保存的是元素,在代码实现中保存的是下标,方便计算)
(1)先求每个元素的左边界left,用数组形式保存其下标
(求左边界时,单调栈的作用就是向左找第一个小于arr[i]的值)
① 对于元素3:
栈为空,直接让3入栈,此时栈中元素为【3,left[0] = 0,表明3的左边界就是它自身
② 对于元素1:
1<栈顶元素3,表明3可能是1的左边界,因此令3出栈,继续找下一个元素
此时栈为空,可以确定3就是1的左边界了,left[1] = 0,表明1的左边界为3
最后需要令1入栈,此时栈中元素为【1,仍然满足单调栈的性质
③对于元素2:
2>栈顶元素1,令left[2] = 2,表明2的左边界就是它自身,
再令2入栈,此时栈中元素为【1,2
④对于元素1:
1<栈顶元素2,需要令2出栈以满足单调栈的特性,此时栈中元素为【1,
1 = 栈顶元素1,再出栈,此时栈为空,令left[3] = 0,表明对于最后一个1而言其左边界是第一个元素3
(2)右边界right同理,区别是从右往左求解
最后解释一下
”“”“”“”“”
需要找到arr[i]的左边界left和右边界right,满足:
① arr[left]……arr[i-1]都大于等于arr[i]
② arr[i+1]……arr[right]都大于arr[i]
”“”“”“”“
比如对于区间[3,1,2,1]
对于第一个1,其子数组有[3,1],[3,1,2],[3,1,2,1],[1],[1,2],[1,2,1]
对于第二个1,其子数组有[3,1,2,1],[1,2,1],[2,1]
可以看到两个1都有重复的子数组[3,1,2,1],[1,2,1]
但只要按照上面讲的方式找左右边界,对于第一个1,是不会统计[3,1,2,1]和[1,2,1],因此可以避免重复计算
代码
class Solution {
public:
int sumSubarrayMins(vector<int>& arr) {
int n = arr.size();
long long int ans = 0;
long long int mod = 1e9+7;
//单调栈,存下标
vector<int> st;
//左右边界,存下标
vector<int> left(n);
vector<int> right(n);
//从左往右,计算arr[i]的左边界
for(int i=0;i<n;i++){
while(!st.empty()&&arr[i]<=arr[st.back()])
st.pop_back();
left[i] = st.empty()?0:st.back()+1;
st.push_back(i);
}
//左边界求完了,单调栈可以清空,重复利用
st.clear();
for(int i=n-1;i>=0;i--){
while(!st.empty()&&arr[i]<arr[st.back()])
st.pop_back();
right[i] = st.empty()?n-1:st.back()-1;
st.push_back(i);
}
for(int i=0;i<n;i++){
//结果溢出,所以两个mod
ans = (ans+((i-left[i]+1)*(right[i]-i+1))%mod*arr[i])%mod;
}
return ans;
}
};
总结
又是一道看了题解才会的题,哪怕告诉我要用到单调栈,也不知道怎么解决;
根本原因是对单调栈只知意思,但不会用,不理解单调栈的应用场景;
通过这一题可以发现单调栈可以解决沿某个方向找比当前值更大或更小的值的位置,学到了,希望下次见到类似的题能记得并会用。