Introduce the “small but mighty” algorithm — monotonic stack and its five properties.
- Next Smaller Element
- Previous Not Greater Element
- Minimum in Subarray
- Monotonic Increasing Stack
- Offline RMQ
Discussed the following problems with monotonic stack:
- Find next greater or smaller elements.
- Find subarrays for an element being minimum or maximum.
- Stack-sortable Permutation, 231 Pattern.
- Lexicorgraphically smallest subsequence after removing some elements.
Problem List
496. Next Greater Element I
1019. Next Greater Node In Linked List
503. Next Greater Element II
739. Daily Temperatures
1475. Final Prices With a Special Discount in a Shop
1762. Buildings With an Ocean View
901. Online Stock Span
2334. Subarray With Elements Greater Than Varying Threshold
1944. Number of Visible People in a Queue
42. Trapping Rain Water
84. Largest Rectangle in Histogram
85. Maximal Rectangle
1856. Maximum Subarray Min-Product
1063. Number of Valid Subarrays
907. Sum of Subarray Minimums
456. 132 Pattern
402. Remove K Digits
316. Remove Duplicate Letters
1081. Smallest Subsequence of Distinct Characters
2030. Smallest K-Length Subsequence With Occurrences of a Letter
Introduction to Monotonic Stack
Monotonic Increasing Stack
Definition
For an array A
, the length of A
is len(A)
. The monotonic stack S
of A
is computed by:
- for
k
in[0, len(A))
:- while
S
is not empty and the top element of SS.top
hasA[S.top] > A[k]
:- pop the top of
S
- pop the top of
- push
k
to the top ofS
- while
Take the array [3, 10, 12, 8, 6, 10]
as an example:
1 | ┌─┐ |
i = 0
,s = []
. The stack is empty. No element is popped.3
is pushed into the stack.i = 1
,s = [0]
. The top of the stack is3
.A[i] = A[1] = 8
is greater than the top of the stack —A[0] = 3
. No element of the stack is popped.1
is pushed into the stack.i = 2
,s = [0, 1]
.A[2] = 12
is greater than the top of the stack —A[1] = 8
. No element of the stack is popped.2
is pushed into the stack.i = 3
,s = [0, 1, 2]
.A[3] = 8
is smaller than the top of the stack —A[2] = 12
. Pop2
out of the stack. The new top of the stack is1
,A[3] = 8
is smaller than the new top of stack —A[1] = 10
. Pop1
out of the stack. The new top of the stack is now0
.A[3] = 8
is now greater than the new top of the stack. Push3
into the stack.i = 4
,s = [0, 3]
.A[4] = 6
is smaller than the top of the stack —A[3] = 8
. Thus,3
is popped.A[4]
is greater than the new top of the stack —A[0] = 3
. Push4
into the stack.i = 5
,s = [0, 4]
.A[5] = 10
is greater than the top of the stack —A[4] = 6
. Push5
into the stack.- The stack is finally
s = [0, 4, 5]
.
Properties
When index
i
in the stack is popped out by indexj
:- PROPERTY 1 - Next Smaller Element(1)
j
is the minimum index such thatj > i
andA[j] < A[i]
(i.e.A[j]
is the next smaller element ofA[i]
). - PROPERTY 2 - Previous Not Greater Element
- After
i
being popped out- if the stack is not empty, the top of the stack
h
is the maximum index such thath < i
andA[h] <= A[a]
(i.e.A[h]
is the previous not greater element ofA[i]
). - if the stack is empty, there is no index
h
such thath < i
andA[h] < A[a]
(i.e. no previous not greater element)
- if the stack is not empty, the top of the stack
- After
- PROPERTY 3 - Minimum in Subarray
- After
i
being popped out, denote the top of the stack ash
- the subarray
R_[h + 1, j)
(ifh
exists) or the subarrayR_[0, j)
(ifh
does not exist) is the subarray of arrayA
such that for all indicesk
in the subarrayR
,R[k] >= A[i]
(i.e.A[i]
is the minimum of the subarrayR
).- However,
A[i]
might not be the only minimum in the subarrayR
. - Subarray
R
is the longest subarray such thatA[i]
is the first minimum. - Subarray
R
might not be the longest subarray that containsA[i]
as one of its minimum elements(Their could be multiple minimums with the same value).R
is the longest whenh
does not exist orA[h] < A[a]
.
- However,
- For two elements (which are indices of array
A
) in the stack ,i
andj
,i < j
. We haveA[k] > A[j] >= A[i]
for allk
in the range(i, j)
.
- After
- PROPERTY 1 - Next Smaller Element(1)
PROPERTY 1 - Next Smaller Element(2) If index
i
is not popped out after all elements of the array are visited, then there is noj
such thatj > i
andA[j] < A[a]
(i.e.A[i]
does not have next smaller element)After pushing the index
k
into the stack, denote the elements (which are indices of the arrayA
) in the stack ass_i
, wherei
is in the range[0, len(stack))
. We have the following properties:s_(len(stack) - 1) == k
(the last element in the stack isk
because we have just pushedk
onto the top of the stack in the last step of the algorithm).- PROPERTY 4 - Monotonic Increasing Stack
[A[s_i]]
is a subsequence of the original array such thatA[s_i] <= A[s_(i - 1)]
(non-decreasing subsequence).[s_i]
is a subsequence of the indices[0, 1, ... k]
such thats_i < s_(i + 1)
(strictly increasing indices)
- PROPERTY 5 - Offline RMQ(Minimum) An RMQ (Range Minimum Query) of the range ends at
k
(inclusive) could be performed withO(Log(len(stack)))
time complexity:- To query the minimum of the range
[j, k]
(j < k
), find the first indexr
in[s_i]
such thatr >= j
, the result of the RMQ isA[r]
. Suchr
could be located with binary search (such asstd::lower_bound
) because[s_i]
is sorted. - RMQs of a known set of ranges could be done in
O(kLog(N))
wherek
is the number of ranges andN
the length of the array(k < N
). This is achieved by:- Sort the
k
ranges by their end,O(kLog(k))
. - Build the monotonic stack and perform RMQs of the sorted ranges,
O(kLog(N))
.
- Sort the
- To query the minimum of the range
The following figures illustrate the 5 properties of the increasing monotonic stack:
- PROPERTY 1 - Next Smaller Element
1 |
|
- PROPERTY 2 - Previous Not Greater Element
- PROPERTY 3 - Minimum in Subarray
1 | ┌─┐ |
- PROPERTY 4 - Monotonic Increasing Stack
- PROPERTY 5 - Offline RMQ(Minimum)
1 | ┌─┐ |
Monotonic Decreasing Stack
Definition
monotonic decreasing stack is similar to the monotonic increasing stack. However, the top of the stack is popped when A[S.top] < A[k]
For an array A
, a monotonic stack S
is a subsequence of the indices array [0, 1, 2, ... j)
, such that S
is built by:
- for
k
in[0, j)
:- while
S
is not empty and the top ofS
hasA[S.top] < A[k]
:- pop the top of
S
- pop the top of
- push
i
to the top ofS
- while
Properties
The properties of the decreasing monotonic stack could be derived by reversing the 5 properties of the increasing monotonic stack.
Usage
Next Greater/Smaller Element
496. Next Greater Element I
1019. Next Greater Node In Linked List
503. Next Greater Element II
739. Daily Temperatures
1475. Final Prices With a Special Discount in a Shop
1762. Buildings With an Ocean View
1 | class Solution { |
This problem uses PROPERTY 1 - Next Smaller(Greater) Element. The name of the problem is self-explanatory. It’s a nice idea to use it to test your code template of the monotonic stack.
Notice that all the remaining elements in the stack are popped out of the stack in lines 14-21. From PROPERTY 1 - Next Smaller Element(2) we could know that these elements do not have their next greater elements.
1019. Next Greater Node In Linked List
1 | /** |
This problem builds the monotonic stack from a linked-list. Monotonic does not require random access to elements in the container. Thus it could be used as an online algorithm.
This problem gives you an array of temperatures. You are asked to find the distance between every element and its next greater element.
1 | class Solution { |
1475. Final Prices With a Special Discount in a Shop
This problem asks you to find the next not greater element instead of the next smaller element. In the line 8 of the code, we should use prices[s.back()] >= price
instead of prices[s.back()] > price
.
1 | class Solution { |
1762. Buildings With an Ocean View
Find the elements that do not have the next greater element. Use PROPERTY 1 - Next Greater (Smaller) Element(2), just return the final stack.
1 | class Solution { |
Previous Not Greater (Not Smaller) Element
This problem asks for an online algorithm to find the previous greater element.
From PROPERTY 2 - Previous Not Greater Element we could know that if we pop the stack whenever the top of the stack is smaller than the current one when the current one is pushed into the stack, the previous element in the stack is the previous not smaller element. Not smaller means greater or equal to.
However, if we only want the previous greater element, we have to pop the stack whenever the top of the stack is smaller than or equal to the current one (line 7 prices_[s_.back()] <= prices_[idx]
in the code).
1 | class StockSpanner { |
Subarrays for an Element being Minimum/Maximum
2334. Subarray With Elements Greater Than Varying Threshold
The problem asks you to find any subarray s
such that every element in the subarray is greater than threshold / len(s)
, where threshold
is a given number and len(s)
is the length of the subarray.
Every element in the subarray is greater than threshold / len(s)
is equivalent to the minimum of the subarray s
is greater than threshold / len(s)
.
Denote the set of all subarrays of A
as {s}
. {s}
could be divided into len(A)
disjoint subsets {s_i}, 0 <= i < len(A)
. the subset of subarrays {s_i}
has the property such that A[i]
is the minimum element and is the first minimum element in the subarray.
In each subset {s_i}
, there exists an element in the subset {s_i}
, the subarray sl_i
, such that sl_i
is the longest subarray in the subset {s_i}
.
Because all the subarrays in the subset {s_i}
shares the same minimum A[i]
. If the longest one of them has A[i] > threshold / len(sl_i)
hold true, then all of them have A[i] > threshold / len
.
Thus for each subarray in the subset {s_i}
, we need only to check the longest one of them.
From PROPERTY 3 - Minimum in Subarray we know that monotonic stack could find out the longest subarray R_i
for each element i
as the first minimum of that subarray. when i
is popped out of the stack. Using PROPERTY 3, we could find the longest subarray with A[i]
as the first minimum — sl_i
for each i
. Then we just check if sl_i
has A[i] > threshold / len(sl_i)
.
1 | class Solution { |
1944. Number of Visible People in a Queue
i
could see j
if i < j
and all the element [i + 1, j)
is smaller than A[i]
and A[j]
.
That is to say, if j
could be seen by i
, then either A[i]
is the maximum element in the subarray [i + 1, j)
or i
is the maximum element in the subarray [i + 1, j)
.
From PROPERTY 3 - Minimum in Subarray, with a monotonic decreasing stack, whenever an element i
is popped out of the stack by another element j
, A[i]
is the first maximum in the subarray [i + 1, j)
. Thus j
could be seen by i
. When j
is pushed into the stack, if the stack is not empty, let the top of the stack as h
then A[h] > A[j]
, h
is the maximum in the subarray [h, j]
. Thus h
could be seen by j
.
1 | class Solution { |
Warning: Solving this problem by monotonic stack is not a good idea.
Considering each bar in the elevation map, the volume of the water that being trapped on the bar trapVolume
could be computed by:
trapVolume = (rightBoundary - leftBoundary) * trapHeight
rightBoundary - leftBoundary
is the length of the subarray such that the bar being the first minimum in the subarray.trapHeight = min(height[leftBoundary], height[leftBoundary]) - midHeight
.
For example, the water trapped on the bar with index 4
is computed in the following picture.
1 | class Solution { |
84. Largest Rectangle in Histogram
For each bar b
in the histogram, the area of the largest rectangle that includes the whole part of b
could be computed by area = height[b] * len(s_b)
. s_b
is the longest subarray such that b
is the first minimum in the subarray.
1 | class Solution { |
1856. Maximum Subarray Min-Product
This problem asks you to find out the largest min-product of all its subarrays. Min-product of an array is the minimum of the array times the sum of the array.
All the numbers in the array are greater than zero. Thus if the minimum of the subarray does not change, the longer the subarray is, the larger the min-product will be.
Denote the set of all subarrays of A
as {s}
. {s}
could be divided into len(A)
disjoint subsets {s_i}, 0 <= i < len(A)
. the subset {s_i}
has the property such that A[i]
is the minimum element and is the first minimum element in the subarray.
For each subset {s_i}
, there exists a subarray sl_i
such that sl_i
is the longest subarray in the subset{s_i}
. All the elements in the subset {s_i}
share the same minimum A[i]
. Therefore, sl_i
must have the largest min-product
among all the subarrays in the subset {s_i}
.
The problem is then solved by finding the largest sl_i
of all subsets {s_i}
.
1 | auto PrefixSum(const vector<int>& nums) { |
Number of such subarrays
From PROPERTY 3 - Minimum(Maximum) in Subarray we could derive the longest subarray for every element i
as the first minimum of the subarray. We could also find the set {s_i}
such that all the subarray in this set shares the same first minimum element A[i]
. But can we find how many subarrays the set {s_i}
has?
Denote sl_i = [a, b); a <= i < b
to be the longest subarray for element i
as the first minimum element. a
and b
could be determined when the element i
is popped out of the stack as described in PROPERTY 3 - Minimum(Maximum) in Subarray.
Then, the number of subarrays in {s_i}
is (i - a + 1) * (b - i)
The number of subarrays that end with i
is (i - a + 1)
.
The number of subarrays that start with i
is (b - i)
.
1063. Number of Valid Subarrays
This problem asks for the number of non-empty subarrays with the leftmost element of the subarray not larger than other elements in the subarray.
From PROPERTY 3 - Minimum(Maximum) in Subarray we could derive the longest subarray sl_i = [a, b); a <= i < b
for every element i
as the first minimum of the subarray. For each element i
, all the subarrays that contain i
, with the leftmost element of the subarrays not larger than other elements in the subarrays, a subset of {s_i}
mentioned previously. The number of such subarrays is n_i = (b_i - i)
. Compute n_i
for each i
and adds them together.
1 | class Solution { |
Find the sum of min(b)
, where b
ranges over every subarray of arr.
This is equivalent to
- for each element in `A`, find the number of the subarrays such that `A[i]` is the first minimum. Then sum all such numbers.
1 | static constexpr const int M = 1e9 + 7; |
Stack-sortable Permutation, 231 Pattern
If a permutation could be sorted using a monotonic (increasing) stack, then this permutation is called stack-sortable permutation. It is proved that Stack-sortable permutations are equivalent to the permutations that do not contain the permutation pattern 231.
This problem asks you to check if the input array contains 132 permutation pattern. 132 is the reverse of 231 pattern. We could simply reverse the input array and check if the reversed array is stack-sortable using a monotonic increasing stack.
1 | class Solution { |
Lexicorgraphically Smallest Subsequence After Removing K Elements
You are given a string s
and an integer k
. Return the lexicographically smallest subsequence of s
after removing at most k
letters.
There exists a greedy strategy using a monotonic increasing stack for this problem. Run monotonic increasing stack algorithm with the following constraints:
- When trying to pop an element from the stack
- If
k > 0
, decreasek
, and pop the element from the stack. - Otherwise, keep the element in the stack.
- If
- After all the letters in the string have been pushed into the stack, if we still have
k > 0
:- Pop the top element from the stack. Decrease
k
for each popped element. Do this untilk == 0
.
- Pop the top element from the stack. Decrease
- Return the stack as the result.
This problem gives you a string of digits. It asks you to remove exactly k
digits of the string. The integer represented by the result should be the smallest possible one.
The number represented by a digit string is smallest when the digit string is lexicographically smallest. We could apply the previously mentioned greedy strategy to this problem. There is no difference between removing at most k
and removing exactly k
digits. The more you remove, the lexicographically smaller the result will be.
Notice that the digits remaining in the stack could start with '0'
, you need to remove these zeros.
1 | template<typename It> |
316. Remove Duplicate Letters
1081. Smallest Subsequence of Distinct Characters
Leetcode 1081 and Leetcode 316 are the same.
In this problem, you are asked to return a lexicographically smallest subsequence such that every letter in the original string appears once and only once.
The greedy strategy could still be applied to this problem with adjustment.
- Compute the frequency
freq[c]
of each letterc
in the string. (line 5 - 10, line 29, 35) - Once a letter
c
is in the stack, no more letterc
should be pushed into the stack. (line 21 - 24) - If there is only one letter
c
(freq[c] == 1
), the letterc
should not be popped out of the stack. (line 28)
1 | int CharToIndex(char c) { |
2030. Smallest K-Length Subsequence With Occurrences of a Letter
This problem is similar with 316. Remove Duplicate Letters but with more contraints.
1 | class Solution { |