I am solving a question on LeetCode.com:
Given an array of integers A, find the sum of min(B), where B ranges over every (contiguous) subarray of A. Since the answer may be large, return the answer modulo 10^9 + 7.
Input: [3,1,2,4]
Output: 17
Explanation: Subarrays are [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4], [3,1,2,4]. Minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1. Sum is 17.
A highly upvoted solution is as below:
class Solution {
public:
int sumSubarrayMins(vector<int>& A) {
stack<pair<int, int>> in_stk_p, in_stk_n;
// left is for the distance to previous less element
// right is for the distance to next less element
vector<int> left(A.size()), right(A.size());
//initialize
for(int i = 0; i < A.size(); i++) left[i] = i + 1;
for(int i = 0; i < A.size(); i++) right[i] = A.size() - i;
for(int i = 0; i < A.size(); i++){
// for previous less
while(!in_stk_p.empty() && in_stk_p.top().first > A[i]) in_stk_p.pop();
left[i] = in_stk_p.empty()? i + 1: i - in_stk_p.top().second;
in_stk_p.push({A[i],i});
// for next less
while(!in_stk_n.empty() && in_stk_n.top().first > A[i]){
auto x = in_stk_n.top();in_stk_n.pop();
right[x.second] = i - x.second;
}
in_stk_n.push({A[i], i});
}
int ans = 0, mod = 1e9 +7;
for(int i = 0; i < A.size(); i++){
ans = (ans + A[i]*left[i]*right[i])%mod;
}
return ans;
}
};
My question is: what is the intuition behind using a monotonically increasing stack for this? How does it help calculate the minimums in the various subarrays?
Monotonic stacks are often used to track the maximum or minimum element in a data set. For example, let's say you have an array of integers and you want to keep track of the largest integer in the array at each index. You could use a monotonic stack for this.
Because it only updates information within the range based on new adding elements and avoids repetitive operations of existing elements. To be more precisely, the monotonic stack helps us maintain maximum and and minimum elements in the range and keeps the order of elements in the range.
Monotonic stack is a stack that from top to bottom, elements are monotonically increasing. From right to left, we can check whether the top of the stack is bigger than the one in array. If it is not bigger than the one in array, it cannot be the solution, since the one in array is farther than it.
A real function or sequence is called monotonic if it either constantly increases or decreases. Thus, the sequence of powers of 2 is monotonically increasing because each term is larger than the previous. The function is monotonically decreasing on the interval and monotonically increasing on the interval .
Visualize the array as a line graph, with (local) minima as valleys. Each value is relevant for a range that extends from just after the previous smaller value (if any) to just before the next smaller value (if any). (Even a value larger than its neighbors matters when considering the singleton subarray that contains it.) The variables left
and right
track that range.
Recognizing that a value shadows every value larger than it in each direction separately, the stack maintains a list of previous, unshadowed minima for two purposes: identifying how far back a new small number’s range extends and (at the same time) how far forward the invalidated minima’s ranges extend. The code uses a separate stack for each purpose, but there’s no need: each has the same contents after every iteration of the (outer) loop.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With