Skip to content

Add monotonous stack #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- [滑动窗口思想](https://github.com/greyireland/algorithm-pattern/blob/master/advanced_algorithm/slide_window.md)
- [二叉搜索树](https://github.com/greyireland/algorithm-pattern/blob/master/advanced_algorithm/binary_search_tree.md)
- [回溯法](https://github.com/greyireland/algorithm-pattern/blob/master/advanced_algorithm/backtrack.md)
- [单调栈](./advanced_algorithm/monotonous_stack.md)

## 心得体会

Expand Down
151 changes: 151 additions & 0 deletions advanced_algorithm/monotonous_stack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# 单调栈

## 介绍

简单来讲单调栈就是里面所有值都是单调递增或者递减的栈,这里先拿递增来举例说明,下面是一个典型的单调递增栈的模板:

```
for(int i = 0; i < A.size(); i++){
while(!in_stk.empty() && in_stk.top() > A[i]){
in_stk.pop();
}
in_stk.push(A[i]);
}
```

这种数据结构能干什么呢?想象下如果有一个数组, `[3, 7, 8, 4]`,要在 O(N) 的时间内找到每个元素前面或者后面的最小值该怎么去做?这就是单调栈的作用

- 找到元素前面的最小值,`PLE`(Previous Less Element)。

以上面的数组为例:

7 的 PLE 为 3,

8 的 PLE 为 7,

4 的 PLE 为 3,

如果我们不直接把值塞进去,而是把数组下表扔进栈,那么我们就可以获取到一个新的 PLE 数组。如下:
```
// previous_less[i] = j means A[j] is the previous less element of A[i].
// previous_less[i] = -1 means there is no previous less element of A[i].
vector<int> previous_less(A.size(), -1);
for(int i = 0; i < A.size(); i++){
while(!in_stk.empty() && A[in_stk.top()] > A[i]){
in_stk.pop();
}
previous_less[i] = in_stk.empty()? -1: in_stk.top();
in_stk.push(i);
}
```

- 找到元素后面的最小值,`NLE`(Next Less Element)

同理可以获取到下面的模板

```
// next_less[i] = j means A[j] is the next less element of A[i].
// next_less[i] = -1 means there is no next less element of A[i].
vector<int> next_less(A.size(), -1);
for(int i = 0; i < A.size(); i++){
while(!in_stk.empty() && A[in_stk.top()] > A[i]){
auto x = in_stk.top();
in_stk.pop();
next_less[x] = i;
}
in_stk.push(i);
}
```

## 应用

[sum-of-subarray-minimums](https://leetcode-cn.com/problems/sum-of-subarray-minimums/)

> 子数组的最小值之和:给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组。

利用上述方法求出某个元素距离 `PLE` 和 `NLE` 之间的距离,然后对 `A[i]*left[i]*right[i]` 进行累加。
```go
func sumSubarrayMins(A []int) int {
mod := int(1e9+7)
stack := make([]int, 0, len(A))
left, right := make([]int, len(A)), make([]int, len(A))

// 初始化 防止最小值没有处理的情况
for i := 0; i < len(A); i += 1 {
left[i] = i + 1
right[i] = len(A)-i
}

for i := 0; i < len(A); i += 1 {
for len(stack) != 0 && A[stack[len(stack)-1]] > A[i] {
last := stack[len(stack)-1]
right[last] = i - last
stack = stack[:len(stack)-1]
}
if len(stack) != 0 {
left[i] = i - stack[len(stack)-1]
}
stack = append(stack, i)
}
// 累加
var sum int
for i := 0; i < len(A); i += 1 {
sum = (sum + A[i]*left[i]*right[i]) % mod
}
return sum
}
```

[next-greater-element-ii](https://leetcode-cn.com/problems/next-greater-element-ii/)

> 下一个更大元素 II:给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

这是一个很基础的单调递减栈,主要的问题在于这个是一个循环数组,所以需要遍历两次,第二次遍历的时候不再入栈.
```go
func nextGreaterElements(nums []int) []int {
if len(nums) == 0 {
return nil
}
stack, ret := make([]int, 0, len(nums)), make([]int, len(nums))
// 第一遍遍历初始化
for i := range ret {
ret[i] = -1
}
// 这里会遍历两遍,只有第一次遍历的时候数据才会入栈
for i := 0; i < len(nums) * 2; i += 1 {

i := i % len(nums)
for len(stack) != 0 && nums[stack[len(stack)-1]] < nums[i] {
ret[stack[len(stack)-1]] = nums[i]
stack = stack[:len(stack)-1]
}

if i < len(nums) {
stack = append(stack, i)
}

}
return ret
}
```

[largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/)

> 给定 _n_ 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
> 求在该柱状图中,能够勾勒出来的矩形的最大面积

在[栈的练习](../data_structure/stack_queue.md)中已经出现过了

## 练习

- [ ] [daily-temperatures](https://leetcode-cn.com/problems/daily-temperatures/)
- [ ] [remove-k-digits](https://leetcode-cn.com/problems/remove-k-digits/)
- [ ] [maximal-rectangle](https://leetcode-cn.com/problems/maximal-rectangle/)
- [ ] [trapping-rain-water](https://leetcode-cn.com/problems/trapping-rain-water/)(challenge)
- [ ] [remove-duplicate-letters](https://leetcode-cn.com/problems/remove-duplicate-letters/)(challenge)
- [ ] [create-maximum-number](https://leetcode-cn.com/problems/create-maximum-number/)
- [ ] [max-chunks-to-make-sorted-ii](https://leetcode-cn.com/problems/max-chunks-to-make-sorted-II/)
- [ ] [sliding-window-maximum](https://leetcode-cn.com/problems/sliding-window-maximum/)(challenge)

## Ref:
- https://leetcode.com/problems/sum-of-subarray-minimums/discuss/178876/stack-solution-with-very-detailed-explanation-step-by-step