单调栈 monotonic stack monostack
小结:
1)
定义
单调栈
对于一列数,所有元素都要入栈,且栈中元素严格单调,即无相等元素。
为了保证栈的严格单调性,就需要考虑已入栈的元素被弹出;
1)新加入元素不大于栈顶元素,则先弹出栈顶元素再入栈;反之,直接入栈。
注意:可能出栈多次,才能入栈。
2)单调性是严格的,栈中无相等的元素。
分析:
遍历每根柱子,
求出高度不低于该高度的柱子(注意不是该根柱子)且包含该高度柱子的柱子连续区间的柱子数(柱子宽均为1),该高度*柱子数=面积。
解:
建立单调递增栈,将柱子高度按顺序入栈。
栈顶元素被弹出前,次栈顶元素的下标+1(如果,栈中只有一个元素,则为第一个元素)即当前连续区间的左边界,将栈顶元素的下标做右边界,求出当前连续区间的面积,和历史最大值比较,即当前此时最大面积;
遍历至最后一根柱子,即的最大面积。
[2,3,1]->(2),(2,3),(1) = (0),(0,1),(2)
[2,5,6,3]->(2),(2,5),(2,5,6),(2,3) = (0),(0,1),(0,1,2),(0,3)
func MonoStackTemperature(l []int) []int {
n := len(l)
ans := make([]int, n, n)
// 建立单调栈
mono := make([]int, 1, 1)
for i := 1; i < n; i++ {
e := l[i]
// 元素入栈
m := len(mono)
for ; m > 0; m-- {
if mono[m-1] > e {
break
} else {
mono = mono[0 : m-1]
}
}
mono = append(mono, e)
// 业务逻辑
for j := 0; j < i; j++ {
if ans[j] == 0 {
if e > l[j] {
ans[j] = i - j
}
}
}
}
return ans
}
func Test_MonoStackTemperature(t *testing.T) {
type args struct {
l []int
}
tests := []struct {
name string
args args
want []int
}{
/*
[2,3,1]->(2),(2,3),(1) = (0),(0,1),(2)-->|1,0,0|
[2,5,6,3]->(2),(2,5),(2,5,6),(2,3) = (0),(0,1),(0,1,2),(0,3)-->|1,1,0,0|
[2,6,5,3]->(2),(2,6),(2,5),(2,3) = (0),(0,1),(0,2),(0,3)-->|1,0,0,0|
[3,2,4]->(3),(2),(2,4) = (0),(1),(1,2)--->|2,1,0|
[8,9,3,2,4]->(8),(8,9),(3),(2),(2,4) = (0),(0,1),(2),(3),(3,4)-->|1,0,2,1,0|
[1,2,3]->(0),(0,1),(0,1,2)-->(1,1,0)
*/
// TODO: Add test cases.
{name: "1", args: args{l: []int{2, 3, 1}}, want: []int{1, 0, 0}},
{name: "2", args: args{l: []int{2, 5, 6, 3}}, want: []int{1, 1, 0, 0}},
{name: "3", args: args{l: []int{2, 6, 5, 3}}, want: []int{1, 0, 0, 0}},
{name: "4", args: args{l: []int{3, 2, 4}}, want: []int{2, 1, 0}},
{name: "5", args: args{l: []int{8, 9, 3, 2, 4}}, want: []int{1, 0, 2, 1, 0}},
{name: "6", args: args{l: []int{1, 2, 3}}, want: []int{1, 1, 0}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := MonoStackTemperature(tt.args.l); !reflect.DeepEqual(got, tt.want) {
t.Errorf("MonoStackTemperature() = %v, want %v", got, tt.want)
}
})
}
}
注意:上述单调栈没有发挥作用。
选单增、减?
是否严格单调?
func MonoStackTemperature(l []int) []int {
n := len(l)
ans := make([]int, n, n)
// 建立单调栈 增?减
// 怎么选择? :减
mono := make([]int, 1, 1)
for i := 1; i < n; i++ {
e := l[i]
// 元素入栈
m := len(mono)
for ; m > 0; m-- {
if l[mono[m-1]] < e {
// 出栈
// 注意::::这里有重复元素了,不是严格单调了
p := mono[m-1]
ans[p] = i - p
mono = mono[0 : m-1]
} else {
break
}
}
mono = append(mono, i)
}
return ans
}
{name: "1", args: args{l: []int{2, 3, 1}}, want: []int{1, 0, 0}},
{name: "2", args: args{l: []int{2, 5, 6, 3}}, want: []int{1, 1, 0, 0}},
{name: "3", args: args{l: []int{2, 6, 5, 3}}, want: []int{1, 0, 0, 0}},
{name: "4", args: args{l: []int{3, 2, 4}}, want: []int{2, 1, 0}},
{name: "5", args: args{l: []int{8, 9, 3, 2, 4}}, want: []int{1, 0, 2, 1, 0}},
{name: "6", args: args{l: []int{1, 2, 3}}, want: []int{1, 1, 0}},
{name: "7", args: args{l: []int{2, 2, 3}}, want: []int{2, 1, 0}},
{name: "8", args: args{l: []int{2, 2, 1}}, want: []int{0, 0, 0}},
{name: "9", args: args{l: []int{1, 2, 2, 3}}, want: []int{1, 2, 1, 0}},
https://leetcode.cn/problems/next-greater-element-i/solution/xia-yi-ge-geng-da-yuan-su-i-by-leetcode-bfcoj/
https://leetcode.cn/problems/132-pattern/
https://leetcode.cn/problems/daily-temperatures/
https://leetcode.cn/problems/0ynMMM/
https://liuzhenglaichn.gitbook.io/algorithm/monotonic-stack
【西法带你学算法】单调栈解题模板秒杀八道题 https://mp.weixin.qq.com/s/Mb8PAxMj2KLTQ1QrCh8XAA
数据结构-单调栈 https://mp.weixin.qq.com/s/gPv3rndt9wAnglltvfeblw
什么是单调栈
单调栈顾名思义就是满足一定单调性的栈,但是由于栈的特性,单调栈中的元素只能在单调栈的一端进出。
在加入新元素时,如果栈顶元素小于(或大于)新元素,就直接加入,否则不断弹出栈顶元素直到栈顶元素小于(或大于)新元素,这样就能保证栈的单调性。
从一道题说起
LeetCode-84:给定n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1。求在该柱状图中,能够勾勒出来的矩形的最大面积。
那么如何思考这道题呢???
假设我们有6根柱子,高度分别为114514。注意到,我们找到的矩形一定有一个起始柱子和一个终止柱子,我们的矩形就是在这两个柱子之间找到的。并且最大矩形的高,一定是某一个柱子的高,这个可以用反证法很容易得到。
有了这些,我们来考虑一下以某一个柱子的高度为基准的情况。
假设我们以第三根柱子(高度为4)为基准,我们要得到的起始柱子和终止柱子,其实就是以这根柱子为基准向左右扩展得到的左边界和右边界。什么样的柱子能够作为左右边界呢?显然,高度大于或等于基准柱子是基本要求,并且起始柱子和终止柱子之间的所有柱子也都应该要保证这一点。而在基准高度相等的情况下,自然是柱子越多越好,分析到这里,我们就可以把问题转换成找到基准柱子向左右方向第一根高度小于基准柱子的柱子,在这个例子中,起始柱子和终止柱子分别是第3和第4根柱子。现在,就可以利用单调栈求解了。??
如果我们把柱子按顺序放入栈中,会发生什么?我们很容易得到这样的不变式:新加入的元素,一定是栈顶要弹出的元素向右数,第一个小于栈顶元素的元素,栈中第二个元素,一定是栈顶元素向左数,第一个小于栈顶元素的元素。下面分析例子中的情况:
-
? 首先是第1根柱子,高度为1,此时栈为空,压入。
-
? 第2根柱子高度与第1根柱子相等,弹出第1根,栈为空,压入。
-
? 第3根柱子比第2根柱子大,压入,此时栈中有第2根柱子和第3根柱子。
-
? 第4根柱子比第3根柱子大,压入,此时栈中有第2根柱子、第3根柱子和第4根柱子。
-
? 第5根柱子比第4根柱子小,弹出第4根柱子,弹出的时候分析第4根柱子,这时候显然第4根柱子的终止柱子是第4根柱子,也就是
5-1=4,因为新加入的元素是向右第一个小于栈顶元素的元素。而起始柱子呢?其实就是弹出栈顶元素后的栈顶元素的位置加上1,也就是3+1=4,那么很容易得到,以第4根柱子为基准的矩形最大是(4-4+1)*4=4。 -
? 重复这个过程。
最后在所有的元素都完成入栈了以后,可以对剩下没有出栈的元素进行出栈操作。