题目来源
题目描述
class Solution {
public:
long long subArrayRanges(vector<int>& nums) {
}
};
题目解析
单调栈
因为需要最大元素、最小元素,所以应该想办法把问题转换为单调栈。
由于我们要求的是所有子数组最大元素和最小元素的差值。因此,我们考虑每个元素成为了多少个区间的最大值,还有多少个区间的最小值。前者的数量就是该元素在累计求和中被加的次数,后者则是被减的次数。
这样我们只需要一种遍历一遍的方式获得每个元素是多少个区间的最大最小值即可在O(n)的时间里求出结果。
考虑一个具体的例子: [ 2 , 3 , 5 , 4 , 10 , 2 ] [2, 3, 5, 4, 10, 2] [2,3,5,4,10,2]
- 我们考虑中间的某个数字4,它是多少区间的最大值呢?首先,左侧的5,就决定了,4为最大值的区间左边界不可能比5靠左;右侧的10也做出了同样的限制。所以4只能是自己这个区间的最大值。
- 那他又是多少区间的最小值呢,我们往左看,第一个小于4的值是3,所以5开始往右的位置可以做左边界;右边第一个小于4的值是2,所以10往左的位置可以做右边界。
- 假设当前值下标为i;左侧第一个小于该元素的下标为l,右侧为r。则以i为最小值的区间数量,通过组合计数原理,可以得到,为 ( i − l ) ∗ ( r − i ) (i-l)*(r-i) (i−l)∗(r−i)
而最小值怎么维护呢?我们用一个单调增栈即可,从左往右遍历,如果栈顶元素大于当前值,我们就出栈;直到栈顶元素小于当前值,这样我们就知道了小于当前值最近的左侧元素是在哪了。
右侧和最大值的计算过程类似。
还有一个小细节,就是数组中会有重复的元素;为了避免重复区间的计算,从左往右和从右往左的统计,我们需要分别用开区间和闭区间来表示。
class Solution {
public:
long long subArrayRanges(vector<int>& nums) {
int m = nums.size();
std::vector<int> lsmall(m, 0);
std::vector<int> rsmall(m, 0);
std::vector<int> llarge(m, 0);
std::vector<int> rlarge(m, 0);
std::stack<int> s; //从左往右单调增栈
// 从左往右单调增栈 不能出栈的时候栈顶就是当前元素左侧最近的小于当前元素的节点
s.push(-1);
for (int i = 0; i < m; ++i) {
while (s.top() != -1 && nums[s.top()] >= nums[i]) {
s.pop();
}
lsmall[i] = s.top();
s.push(i);
}
// 从右往左单调增栈 不能出栈的时候栈顶就是当前元素右侧最近的小于当前元素的节点
s = stack<int>();
s.push(m);
for (int i = m-1; i >= 0; i--) {
while (s.top() != m && nums[s.top()] > nums[i]) {
s.pop();
}
rsmall[i] = s.top();
s.push(i);
}
// 从左往右单调减栈 不能出栈的时候栈顶就是当前元素左侧最近的大于当前元素的节点
s = stack<int>();
s.push(-1);
for (int i = 0; i < m; i++) {
while (s.top() != -1 && nums[s.top()] <= nums[i]) {
s.pop();
}
llarge[i] = s.top();
s.push(i);
}
// 从右往左单调增栈 不能出栈的时候栈顶就是当前元素右侧最近的大于当前元素的节点
s = stack<int>();
s.push(m);
for (int i = m-1; i >= 0; i--) {
while (s.top() != m && nums[s.top()] < nums[i]) {
s.pop();
}
rlarge[i] = s.top();
s.push(i);
}
long long ans = 0;
for (int i = 0; i < m; ++i) {
ans += nums[i] * 1L * (i - llarge[i]) * (rlarge[i] - i);
ans -= nums[i] * 1L * (i - lsmall[i]) * (rsmall[i] - i);
}
return ans;
}
};