|
| 1 | +# 单调栈 |
| 2 | + |
| 3 | +## 介绍 |
| 4 | + |
| 5 | +简单来讲单调栈就是里面所有值都是单调递增或者递减的栈,这里先拿递增来举例说明,下面是一个典型的单调递增栈的模板: |
| 6 | + |
| 7 | +``` |
| 8 | +for(int i = 0; i < A.size(); i++){ |
| 9 | + while(!in_stk.empty() && in_stk.top() > A[i]){ |
| 10 | + in_stk.pop(); |
| 11 | + } |
| 12 | + in_stk.push(A[i]); |
| 13 | +} |
| 14 | +``` |
| 15 | + |
| 16 | +这种数据结构能干什么呢?想象下如果有一个数组, `[3, 7, 8, 4]`,要在 O(N) 的时间内找到每个元素前面或者后面的最小值该怎么去做?这就是单调栈的作用 |
| 17 | + |
| 18 | +- 找到元素前面的最小值,`PLE`(Previous Less Element)。 |
| 19 | + |
| 20 | + 以上面的数组为例: |
| 21 | + |
| 22 | + 7 的 PLE 为 3, |
| 23 | + |
| 24 | + 8 的 PLE 为 7, |
| 25 | + |
| 26 | + 4 的 PLE 为 3, |
| 27 | + |
| 28 | + 如果我们不直接把值塞进去,而是把数组下表扔进栈,那么我们就可以获取到一个新的 PLE 数组。如下: |
| 29 | +``` |
| 30 | +// previous_less[i] = j means A[j] is the previous less element of A[i]. |
| 31 | +// previous_less[i] = -1 means there is no previous less element of A[i]. |
| 32 | +vector<int> previous_less(A.size(), -1); |
| 33 | +for(int i = 0; i < A.size(); i++){ |
| 34 | + while(!in_stk.empty() && A[in_stk.top()] > A[i]){ |
| 35 | + in_stk.pop(); |
| 36 | + } |
| 37 | + previous_less[i] = in_stk.empty()? -1: in_stk.top(); |
| 38 | + in_stk.push(i); |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +- 找到元素后面的最小值,`NLE`(Next Less Element) |
| 43 | + |
| 44 | +同理可以获取到下面的模板 |
| 45 | + |
| 46 | +``` |
| 47 | +// next_less[i] = j means A[j] is the next less element of A[i]. |
| 48 | +// next_less[i] = -1 means there is no next less element of A[i]. |
| 49 | +vector<int> next_less(A.size(), -1); |
| 50 | +for(int i = 0; i < A.size(); i++){ |
| 51 | + while(!in_stk.empty() && A[in_stk.top()] > A[i]){ |
| 52 | + auto x = in_stk.top(); |
| 53 | + in_stk.pop(); |
| 54 | + next_less[x] = i; |
| 55 | + } |
| 56 | + in_stk.push(i); |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +## 应用 |
| 61 | + |
| 62 | +[sum-of-subarray-minimums](https://leetcode-cn.com/problems/sum-of-subarray-minimums/) |
| 63 | + |
| 64 | +> 子数组的最小值之和:给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组。 |
| 65 | + |
| 66 | +利用上述方法求出某个元素距离 `PLE` 和 `NLE` 之间的距离,然后对 `A[i]*left[i]*right[i]` 进行累加。 |
| 67 | +```go |
| 68 | +func sumSubarrayMins(A []int) int { |
| 69 | + mod := int(1e9+7) |
| 70 | + stack := make([]int, 0, len(A)) |
| 71 | + left, right := make([]int, len(A)), make([]int, len(A)) |
| 72 | + |
| 73 | + // 初始化 防止最小值没有处理的情况 |
| 74 | + for i := 0; i < len(A); i += 1 { |
| 75 | + left[i] = i + 1 |
| 76 | + right[i] = len(A)-i |
| 77 | + } |
| 78 | + |
| 79 | + for i := 0; i < len(A); i += 1 { |
| 80 | + for len(stack) != 0 && A[stack[len(stack)-1]] > A[i] { |
| 81 | + last := stack[len(stack)-1] |
| 82 | + right[last] = i - last |
| 83 | + stack = stack[:len(stack)-1] |
| 84 | + } |
| 85 | + if len(stack) != 0 { |
| 86 | + left[i] = i - stack[len(stack)-1] |
| 87 | + } |
| 88 | + stack = append(stack, i) |
| 89 | + } |
| 90 | + // 累加 |
| 91 | + var sum int |
| 92 | + for i := 0; i < len(A); i += 1 { |
| 93 | + sum = (sum + A[i]*left[i]*right[i]) % mod |
| 94 | + } |
| 95 | + return sum |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +[next-greater-element-ii](https://leetcode-cn.com/problems/next-greater-element-ii/) |
| 100 | + |
| 101 | +> 下一个更大元素 II:给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。 |
| 102 | + |
| 103 | +这是一个很基础的单调递减栈,主要的问题在于这个是一个循环数组,所以需要遍历两次,第二次遍历的时候不再入栈. |
| 104 | +```go |
| 105 | +func nextGreaterElements(nums []int) []int { |
| 106 | + if len(nums) == 0 { |
| 107 | + return nil |
| 108 | + } |
| 109 | + stack, ret := make([]int, 0, len(nums)), make([]int, len(nums)) |
| 110 | + // 第一遍遍历初始化 |
| 111 | + for i := range ret { |
| 112 | + ret[i] = -1 |
| 113 | + } |
| 114 | + // 这里会遍历两遍,只有第一次遍历的时候数据才会入栈 |
| 115 | + for i := 0; i < len(nums) * 2; i += 1 { |
| 116 | + |
| 117 | + i := i % len(nums) |
| 118 | + for len(stack) != 0 && nums[stack[len(stack)-1]] < nums[i] { |
| 119 | + ret[stack[len(stack)-1]] = nums[i] |
| 120 | + stack = stack[:len(stack)-1] |
| 121 | + } |
| 122 | + |
| 123 | + if i < len(nums) { |
| 124 | + stack = append(stack, i) |
| 125 | + } |
| 126 | + |
| 127 | + } |
| 128 | + return ret |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +[largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) |
| 133 | + |
| 134 | +> 给定 _n_ 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 |
| 135 | +> 求在该柱状图中,能够勾勒出来的矩形的最大面积 |
| 136 | + |
| 137 | +在[栈的练习](../data_structure/stack_queue.md)中已经出现过了 |
| 138 | + |
| 139 | +## 练习 |
| 140 | + |
| 141 | +- [ ] [remove-k-digits](https://leetcode-cn.com/problems/remove-k-digits/) |
| 142 | +- [ ] [maximal-rectangle](https://leetcode-cn.com/problems/maximal-rectangle/) |
| 143 | +- [ ] [trapping-rain-water](https://leetcode-cn.com/problems/trapping-rain-water/)(challenge) |
| 144 | +- [ ] [remove-duplicate-letters](https://leetcode-cn.com/problems/remove-duplicate-letters/)(challenge) |
| 145 | +- [ ] [create-maximum-number](https://leetcode-cn.com/problems/create-maximum-number/) |
| 146 | +- [ ] [max-chunks-to-make-sorted-ii](https://leetcode-cn.com/problems/max-chunks-to-make-sorted-II/) |
| 147 | +- [ ] [sliding-window-maximum](https://leetcode-cn.com/problems/sliding-window-maximum/)(challenge) |
| 148 | + |
| 149 | +## Ref: |
| 150 | +- https://leetcode.com/problems/sum-of-subarray-minimums/discuss/178876/stack-solution-with-very-detailed-explanation-step-by-step |
0 commit comments