# Leetcode刷題學習筆記 -- Monotonic Stack
## 簡介
stack中保持著一定的順序,例如遞增或是遞減。
如果要新增的element比top還大/小。就把top pop出去。然後繼續往下比,直到保持一定的遞增或是遞減的順序。通常是找序列中比自己後面,但是值比自己還大/小的題目。
## 使用時機?
### 1. 需要==往前或是往後==尋找,==下一個比自己大或比自己小==的元素就可以使用monotonic stack
#### PLE(Previous Less Element)
Good reference : [Sum of Subarray Minimums](https://leetcode.com/problems/sum-of-subarray-minimums/solutions/178876/stack-solution-with-very-detailed-explanation-step-by-step/?orderBy=most_votes)
找出之前比自己還小的值。
+ stack 存放index。因為存index有很多好處,其中之一就是可以算和target的差距。
+ 如果stack為empty則填入-1,也是有利計算差距。
```cpp
vector<int> nums;
vector<int> ple(nums.size());
stack<int> st; // 存放index
for(int i = 0; i < nums.size(); ++i) {
while(!st.empty() && nums[st.top()] > nums[i]) st.pop();
ple[i] = st.empty() ? -1 : st.top();
st.push(i); // 將目前index推入stack
}
```
#### NLE(Next less element)
找出之後比自己還小的值。
+ code snippet一樣,只是for反過來尋找
+ 如果stack為empty則填入nums.size()
```cpp
vector<int> nums;
int sz = nums.size();
vector<int> nle(sz);
stack<int> st;
for(int i = nums.size() - 1; i >= 0; --i) {
while(!st.empty() && nums[st.top()] > nums[i]) st.pop();
nle[i] = st.empty() ? sz : st.top();
st.push(i);
}
```
另一種寫法,如果沒辦法從後面走訪數列的話
```cpp
vector<int> nums;
int sz = nums.size();
vector<int> nle(sz, sz);
stack<int> st;
for(int i = 0; i < sz; ++i) {
while(!st.empty() && nums[st.top()] > nums[i]) {
nle[st.top()] = i;
st.pop();
}
st.push(i);
}
```
### 2. 往前或是往後看的最大/小值
剛剛第一種情況是往前或是往後看,第一個比自己大或小的值。另一種情況是往前看或是往後看,找出最大值。如果自己本身是最大值就是自己。
以下的code是從自己往後看,看到最大值的index。
```cpp=
vector<int> nums;
int sz = nums.size();
vector<int> bwdMax(sz); // index
stack<int> st; // index
for(int i = 0; i < sz; ++i) {
// stack中比目前的值還小,就丟掉,因為接下來的index只會看到這個
while(!st.empty() && nums[i] >= nums[st.top()]) st.pop();
if(st.empty()) st.push(i); // 如果沒了就是看到自己
bwdMax[i] = st.top();
}
```
以下的code使從自己往前看,看到最大值的index。
```cpp=
vector<int> nums;
int sz = nums.size();
vector<int> fwdMax(sz); // index
stack<int> st; // index
for(int i = sz - 1; i >= 0; --i) {
while(!st.empty() && nums[i] >= nums[st.top()]) st.pop();
if(st.empty()) st.push(i);
fwdMax[i] = st.top();
}
```
後來想想,其實找fordward max或是backward max不需要使用stack,只要用以下簡單的方法即可。
```cpp=
int maxv = -1;
vector<int> bwd(sz);
for(int i = 0; i < sz; ++i) {
maxv = max(maxv, nums[i]);
bwd[i] = maxv;
}
```
### Example
#### [42. Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/)
給你一個vector<int> height,下雨過後會有多少雨水留在裡面。
> 1. 雨水留在裡面的多少會由,這格往前往後看的最大值決定。
> 2. 所以使用monotonic stack來計算每格往前往後看的最大值。
```cpp=
class Solution {
public:
int trap(vector<int>& height) {
auto sz = height.size();
stack<int> st;
vector<int> bwdMax(sz), fwdMax(sz);
// backward max value index
for(int i = 0; i < sz; ++i) {
while(!st.empty() && height[i] >= height[st.top()]) st.pop();
if(st.empty()) st.push(i);
bwdMax[i] = st.top();
}
// clean the stack
while(!st.empty()) st.pop();
// forward max value index
for(int i = sz - 1; i >= 0; --i) {
while(!st.empty() && height[i] >= height[st.top()]) st.pop();
if(st.empty()) st.push(i);
fwdMax[i] = st.top();
}
// calculate each rain
int ans{0};
for(int i = 0; i < sz; ++i) {
ans += min(height[fwdMax[i]], height[bwdMax[i]]) - height[i];
}
return ans;
}
};
```
也可以使用以下簡單的方法
```cpp=
class Solution {
public:
int trap(vector<int>& height) {
int sz = height.size();
vector<int> fwd(sz), bwd(sz);
int maxv = -1;
for(int i = 0; i < sz; ++i) {
maxv = max(maxv, height[i]);
bwd[i] = maxv;
}
maxv = -1;
for(int i = sz - 1; i >= 0; --i) {
maxv = max(maxv, height[i]);
fwd[i] = maxv;
}
int ans{0};
for(int i = 0; i < sz; ++i) {
ans += min(fwd[i], bwd[i]) - height[i];
}
return ans;
}
};
```
### [739. Daily Temperatures[Medium]](https://leetcode.com/problems/daily-temperatures/)
給你一個每日溫度的數列vector<int> temperatures。求出過給天後,會比現在還溫暖。例如:
Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]
解題思路:
> 這是一個典型的單調遞增stack的問題。
```cpp=
vector<int> dailyTemperatures(vector<int>& temperatures) {
stack<pair<int, int>> st; // 使用pair來紀錄
auto n = temperatures.size();
vector<int> ans(n, 0);
for(int i = 0; i < n; ++i) {
if(!st.empty()) {
while(!st.empty() && temperatures[i] > st.top().first) {
auto item = st.top();
ans[item.second] = i - item.second;
st.pop();
}
}
st.push({temperatures[i], i});
}
return ans;
}
```
2022/07/06
和上面的相比,stack只需存index即可。
2022/10/29
因為這題是找出後面第一個比自己大的值,所以是monotonic stack
```cpp=
vector<int> dailyTemperatures(vector<int>& temp) {
stack<int> s; //index
int sz = temp.size();
vector<int> rtn(sz);
for(int i = sz - 1; i >= 0; --i) {
while(!s.empty() && temp[i] >= temp[s.top()])
s.pop();
rtn[i] = s.empty() ? 0 : s.top() - i;
s.push(i);
}
return rtn;
}
```
#### [901. Online Stock Span](https://leetcode.com/problems/online-stock-span/description/)
找出price左邊連續比它還小的數值。
> 1. 一開始有想到monotone stack但是怎麼寫都是不對。原因是少了一個變數。這邊使用了stack<pair<int, int>>。多了一個資訊。統計這個數字的span。全部的span加起來剛好是輸入price的個數。
> 2. 使用monotone stack是因為不需要找比自己還大的數字。
> 3. 可以看成我們在整理這個vector
> [100]
> [100, 80]
> [100, 80, 60]
> [100, 80, [60, 70]] --> [100, 80, 70(2)]
> [100, 80, 70(2), 60]
> [100, 80, [70(2), 60, 75]] --> [100, 80, 75(4)]
> [100, [80, 75(4), 85]] --> [100, 85(6)]
```cpp
class StockSpanner {
stack<pair<int, int>> st; // value, span
public:
StockSpanner() {
}
int next(int price) {
int ret{1};
while(!st.empty() && st.top().first <= price) {
ret += st.top().second;
st.pop();
}
st.emplace(price, ret);
return ret;
}
};
```
2022/10/29
因為這題是找出前面比自己還要大的數,所以使用monotonic stack。
```cpp
class StockSpanner {
vector<int> prices;
stack<int> st;
int idx{0};
public:
StockSpanner() {
}
int next(int price) {
while(!st.empty() && prices[st.top()] <= price) st.pop();
int rtn = st.empty() ? idx + 1: idx - st.top();
prices.push_back(price);
st.push(idx);
idx++;
return rtn;
}
};
```
### 3. 找出以第i個位置的山峰或是山谷
應用前面的找出前面和後面比自己高或是比自己低的位置,如果左右邊都應用的話,就是找出以第i個位置的山峰或是山谷。
例如: [1,4,3,7,4,5],可以畫出以下的圖

這邊可以發現,對index = 2 來說,如果以他為local minimal的subarray,可以得到。

同理,對index = 4來說,以他為local minimal的subarray,可以得到

### Example
#### [1793. Maximum Score of a Good Subarray](https://leetcode.com/problems/maximum-score-of-a-good-subarray/description)
Maximum score的定義是,subarray的最小值,乘上subarray的長度。另外Good subarray的定義是,必須包含index = k。
> 1. 因為Score的定義是,subarray的最小值乘上長度。所以算是找local minimal => 使用monotonic stack找出區域山谷。
> 2. 最後看每個index所構成的區域山谷,使否有包含index = k。
```cpp=
class Solution {
public:
int maximumScore(vector<int>& nums, int k) {
int sz = nums.size();
vector<int> left(sz, -1), right(sz, sz), st;
for(int i = 0; i < sz; ++i) {
while(!st.empty() && nums[i] < nums[st.back()]) {
right[st.back()] = i;
st.pop_back();
}
st.push_back(i);
}
st.clear();
for(int i = sz - 1; i >= 0; --i) {
while(!st.empty() && nums[i] < nums[st.back()]) {
left[st.back()] = i;
st.pop_back();
}
st.push_back(i);
}
int ans{};
for(int i = 0; i < sz; ++i) {
if(left[i] < k && right[i] > k)
ans = max(ans, nums[i] * (right[i] - left[i] - 1));
}
return ans;
}
};
```
#### [84. Largest Rectangle in Histogram](https://leetcode.com/problems/largest-rectangle-in-histogram/)
找出histogram中最大面積的長方形。
> 1. 因為長方形的高度會被,最矮的數值決定。=> 找出以此index的區域山谷。
> 2. 找出以每個index為山谷之後,就可以依序找出每個面積,得到最大值。
```cpp=
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int sz = heights.size();
vector<int> fle(sz, sz), ble(sz, -1);
stack<int> st;
for(int i = 0; i < sz; ++i) {
while(!st.empty() && heights[i] < heights[st.top()]) {
fle[st.top()] = i;
st.pop();
}
st.push(i);
}
st = move(stack<int>());
for(int i = sz - 1; i >= 0; --i) {
while(!st.empty() && heights[i] < heights[st.top()]) {
ble[st.top()] = i;
st.pop();
}
st.push(i);
}
int ans{};
for(int i = 0; i < sz; ++i) {
int left = i - ble[i];
int right = fle[i] - i;
ans = max(ans, heights[i] * (left + right - 1));
}
return ans;
}
};
```
## Problems
### [907. Sum of Subarray Minimums](https://leetcode.com/problems/sum-of-subarray-minimums/description/)
給你一個vector<int>找出所有subarray的組合中,每個subarray的最小值的和為多少?
> + 這題參考了 [Sum of Subarray Minimums](https://leetcode.com/problems/sum-of-subarray-minimums/solutions/178876/stack-solution-with-very-detailed-explanation-step-by-step/?orderBy=most_votes) 對monotinic stack有更深一層的了解。
> + 另外參考了官神的[答案](https://github.com/wisdompeak/LeetCode/tree/master/Stack/907.Sum-of-Subarray-Minimums),因為官神對於計算個數比較直覺。
> 計算個數參考以下的註解。
> + 因為要知道subarray最小值,所以某個subarray的最小值取決於,這個subarray的左邊和右邊數值都是比目前的值還要大。
> + 找出自己前面和後面的最小值 ==> monotonic stack
> + why two pass? 因為條件和自己前後都有關係。
```cpp
// [3, 1, 2, 4]
// [3] --> ans += 3, 找出比自己小的左右兩邊的邊界
// [3, 1], [1], [1, 2], [1, 2, 4], [3, 1, 2], [3, 1, 2, 4] --> ans += 1
// [2], [2, 4] --> ans += 2
// [4] --> ans += 4
int sumSubarrayMins(vector<int>& arr) {
int sz = arr.size();
vector<int> ple(sz), nle(sz);
// PLE
stack<int> st;
for(int i = 0; i < sz; ++i) {
while(!st.empty() && arr[st.top()] > arr[i]) st.pop();
ple[i] = st.empty() ? -1 : st.top();
st.push(i);
}
// clear the stack
while(!st.empty()) st.pop();
// NLE
// 為什麼是大於等於?
// 因為等於的最小值也是和arr[i]一樣。
// 但是如果,兩邊都使用>= 會有重複問題,所以只要一邊就可以。
// ex : [71, 55, 82, 55], 如果ple和nle都使用>=
// idx1 [71, 55], [55],
// [82], [82, 55]
// idx3 [71, 55, 82, 55] -->重複計算
for(int i = sz - 1; i >= 0; --i) {
while(!st.empty() && arr[st.top()] >= arr[i]) st.pop();
nle[i] = st.empty() ? sz : st.top();
st.push(i);
}
// count the result
// [3, 1, 2, 4]
// -1, ,1 ,4
// i - ple[i] : 前面有幾個element : 2 --> [1], [3, 1]
// nle[i] - i : 後面有幾個element : 3 --> [], [2], [2, 4]
long rtn{0};
long mod = 1e9 + 7;
for(int i = 0; i < sz; ++i) {
long count = (i - ple[i]) * (nle[i] - i) % mod;
rtn = (rtn + arr[i] * count) % mod;
}
return rtn;
}
```
### [316. Remove Duplicate Letters](https://leetcode.com/problems/remove-duplicate-letters/description/?envType=daily-question&envId=2023-09-26)
```cpp=
class Solution {
public:
string removeDuplicateLetters(string s) {
stack<char> st;
unordered_set<char> seen;
unordered_map<char, int> last; // 最後出現的位置
for(int i = 0; i < s.size(); ++i) last[s[i]] = i;
for(int i = 0; i < s.size(); ++i) {
if(seen.count(s[i])) continue;
// 目前的char比s[i]還大,且後面還會有,就丟掉
while(!st.empty() && s[i] < st.top() && last[st.top()] > i) {
seen.erase(st.top());
st.pop();
}
seen.insert(s[i]);
st.push(s[i]);
}
string rtn{};
while(!st.empty()) {
rtn = st.top() + rtn;
st.pop();
}
return rtn;
}
};
```
### [2866. Beautiful Towers II](https://leetcode.com/problems/beautiful-towers-ii/description/)
給你一個vector<int> maxHeights,當用每個index當成mountain array的top,則兩邊勢必要配合top做修正,修正完之後才會成為mountain array,返回最大的mountain array sum。
> 1. 參考[官方答案](https://leetcode.cn/problems/beautiful-towers-ii/solutions/2563770/mei-li-ta-ii-by-leetcode-solution-2j4s/)。
> 2. 我們把每個index都當成mountain top。
> 3. 如果兩邊遇到比自己大的就會變成maxHeights[i]。所以可以容易計算出sum += (j - i) * maxHeights[i];
> 4. 遇到比自己小的(j),則會被j的左邊或是右邊的sum決定。
> 5. 所以問題變成找出比自己小的index。
> 6. 另外卡住的點是,以前使用monotonic stack會在pop之後更新資料,但是現在是pop完之後才處理,所以方向改變了。
```cpp=
class Solution {
public:
long long maximumSumOfHeights(vector<int>& maxHeights) {
int sz = maxHeights.size();
vector<long long> right(sz), left(sz);
stack<int> st;
for(int i = 0; i < sz; ++i) {
while(!st.empty() && maxHeights[i] < maxHeights[st.top()])
st.pop();
if(st.empty()) left[i] = (long long)(i + 1) * maxHeights[i];
else left[i] = left[st.top()] + (long long)(i - st.top()) * maxHeights[i];
st.push(i);
}
st = move(stack<int>());
long long res{};
for(int i = sz - 1; i >= 0; --i) {
while(!st.empty() && maxHeights[i] < maxHeights[st.top()])
st.pop();
if(st.empty()) right[i] = (long long)(sz - i) * maxHeights[i];
else right[i] = right[st.top()] + (long long)(st.top() - i) * maxHeights[i];
st.push(i);
res = max(res, left[i] + right[i] - maxHeights[i]);
}
return res;
}
};
```
### [1944.Number of Visible People In a Queue](https://leetcode.com/problems/number-of-visible-people-in-a-queue/description/)
> 1. 這題感覺就是monotonic stack的題目,但是就是思考不出來怎麼處理中間比較矮的人。
> 2. 參考[lee215解答](https://leetcode.com/problems/number-of-visible-people-in-a-queue/solutions/1359707/java-c-python-stack-solution-next-greater-element/)
```cpp=
class Solution {
// 題目是往右看,可以看到幾個人
public:
vector<int> canSeePersonsCount(vector<int>& heights) {
int sz = heights.size();
vector<int> res(sz), st;
for(int i = 0; i < sz; ++i) {
// 前一個比自己小的(st.back()),因為會被我擋住(i),
// 所以丟出來,不用在比了
while(!st.empty() && heights[st.back()] <= heights[i])
res[st.back()]++, st.pop_back();
// 前一個比自己大的(st.back())可以看到自己(i)
if(!st.empty())
res[st.back()]++;
st.push_back(i);
}
return res;
}
};
```
###### tags: `leetcode` `刷題`