From e2d43f710216d627a1f08786049ed0630b21cbe8 Mon Sep 17 00:00:00 2001 From: Lisheng Zheng Date: Tue, 30 Jun 2020 00:24:00 +0800 Subject: [PATCH 1/2] add monotonous stack --- README.md | 1 + advanced_algorithm/monotonous_stack.md | 150 +++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 advanced_algorithm/monotonous_stack.md diff --git a/README.md b/README.md index 7395a655..5190f2c7 100644 --- a/README.md +++ b/README.md @@ -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) ## 心得体会 diff --git a/advanced_algorithm/monotonous_stack.md b/advanced_algorithm/monotonous_stack.md new file mode 100644 index 00000000..82cca09b --- /dev/null +++ b/advanced_algorithm/monotonous_stack.md @@ -0,0 +1,150 @@ +# 单调栈 + +## 介绍 + +简单来讲单调栈就是里面所有值都是单调递增或者递减的栈,这里先拿递增来举例说明,下面是一个典型的单调递增栈的模板: + +``` +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 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 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)中已经出现过了 + +## 练习 + +- [ ] [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 From fa253064b8a5157a59cd60c907cc41f4f0b3f462 Mon Sep 17 00:00:00 2001 From: Lisheng Zheng Date: Tue, 30 Jun 2020 09:39:54 +0800 Subject: [PATCH 2/2] add a basic practice --- advanced_algorithm/monotonous_stack.md | 1 + 1 file changed, 1 insertion(+) diff --git a/advanced_algorithm/monotonous_stack.md b/advanced_algorithm/monotonous_stack.md index 82cca09b..d8f8f7bf 100644 --- a/advanced_algorithm/monotonous_stack.md +++ b/advanced_algorithm/monotonous_stack.md @@ -138,6 +138,7 @@ func nextGreaterElements(nums []int) []int { ## 练习 +- [ ] [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)