1
1
## 1. 单调栈简介
2
2
3
- > ** 单调栈(Monotone Stack)** :一种特殊的栈。在栈的「先进后出」规则基础上,要求「从 ** 栈顶** 到 ** 栈底** 的元素是单调递增(或者单调递减)」。其中满足从栈顶到栈底的元素是单调递增的栈,叫做「单调递增栈」。满足从栈顶到栈底的元素是单调递减的栈,叫做「单调递减栈」。
4
-
5
- 注意:这里定义的顺序是从「栈顶」到「栈底」。有的文章里是反过来的。本文全文以「栈顶」到「栈底」的顺序为基准来描述单调栈。
3
+ > ** 单调栈(Monotone Stack)** :在栈「先进后出」规则的基础上,要求从 ** 栈顶** 到 ** 栈底** 的元素单调递增或单调递减。
4
+ >
5
+ > - ** 单调递增栈** :从栈顶到栈底元素单调递增
6
+ > - ** 单调递减栈** :从栈顶到栈底元素单调递减
6
7
7
8
### 1.1 单调递增栈
8
9
9
- > ** 单调递增栈** :只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈 。
10
+ > ** 单调递增栈** :每次新元素进栈时,如果它比栈顶元素小,直接入栈;如果比栈顶元素大或相等,就把栈顶及以上所有小于等于它的元素依次弹出,直到栈顶比它大或栈空,再将新元素入栈 。
10
11
>
11
- > 这样就保证了:栈中保留的都是比当前入栈元素大的值,并且从栈顶到栈底的元素值是单调递增的 。
12
+ > 这样可以保证:栈从栈顶到栈底的元素是递增的,且每个元素左侧第一个比它大的元素都能被快速找到 。
12
13
13
- 单调递增栈的入栈、出栈过程如下 :
14
+ 单调递增栈的操作流程 :
14
15
15
- - 假设当前进栈元素为 $x$,如果 $x$ 比栈顶元素小,则直接入栈 。
16
- - 否则从栈顶开始遍历栈中元素,把小于 $x$ 或者等于 $x$ 的元素弹出栈,直到遇到一个大于 $x$ 的元素为止,然后再把 $x$ 压入栈中 。
16
+ - 当前元素 $x$,如果 $x$ < 栈顶元素,直接入栈 。
17
+ - 否则,不断弹出栈顶小于等于 $x$ 的元素,直到栈顶比 $x$ 大或栈空,然后将 $x$ 入栈 。
17
18
18
- 下面我们以数组 $[ 2, 7, 5, 4, 6, 3, 4, 2] $ 为例,模拟一下 「单调递增栈」的进栈、出栈过程。具体过程如下 :
19
+ 下面以数组 $[ 2, 7, 5, 4, 6, 3, 4, 2] $ 为例,演示 「单调递增栈」的入栈与出栈过程 :
19
20
20
- - 数组元素 :$[ 2, 7, 5, 4, 6, 3, 4, 2] $,遍历顺序为从左到右 。
21
+ - 数组 :$[ 2, 7, 5, 4, 6, 3, 4, 2] $,从左到右依次遍历 。
21
22
22
- | 第 i 步 | 待插入元素 | 操 作 | 结 果(左侧为栈底) | 作 用 |
23
- | :----- : | :-------- : | ---------------------- | ------------------- | ------------------------------------- |
24
- | 1 | 2 | 2 入栈 | [ 2] | 元素 2 的左侧无比 2 大的元素 |
25
- | 2 | 7 | 2 出栈,7 入栈 | [ 7] | 元素 7 的左侧无比 7 大的元素 |
26
- | 3 | 5 | 5 入栈 | [ 7, 5] | 元素 5 的左侧第一个比 5 大的元素为:7 |
27
- | 4 | 4 | 4 入栈 | [ 7, 5, 4] | 元素 4 的左侧第一个比 4 大的元素为:5 |
28
- | 5 | 6 | 4 出栈,5 出栈,6 入栈 | [ 7, 6] | 元素 6 的左侧第一个比 6 大的元素为:7 |
29
- | 6 | 3 | 3 入栈 | [ 7, 6, 3] | 元素 3 的左侧第一个比 3 大的元素为:6 |
30
- | 7 | 4 | 3 出栈,4 入栈 | [ 7, 6, 4] | 元素 4 的左侧第一个比 4 大的元素为:6 |
31
- | 8 | 2 | 2 入栈 | [ 7, 6, 4, 2] | 元素 2 的左侧第一个比 2 大的元素为:4 |
23
+ | 步骤 | 当前元素 | 操作 | 栈状态(左为栈底) | 说明 |
24
+ | :--: | :------: | ------------------------- | ------------------- | - ------------------------------------- |
25
+ | 1 | 2 | 2 入栈 | [ 2] | 2 左侧无更大元素 |
26
+ | 2 | 7 | 2 出栈,7 入栈 | [ 7] | 7 左侧无更大元素 |
27
+ | 3 | 5 | 5 入栈 | [ 7, 5] | 5 左侧第一个更大元素为 7 |
28
+ | 4 | 4 | 4 入栈 | [ 7, 5, 4] | 4 左侧第一个更大元素为 5 |
29
+ | 5 | 6 | 4 出栈,5 出栈,6 入栈 | [ 7, 6] | 6 左侧第一个更大元素为 7 |
30
+ | 6 | 3 | 3 入栈 | [ 7, 6, 3] | 3 左侧第一个更大元素为 6 |
31
+ | 7 | 4 | 3 出栈,4 入栈 | [ 7, 6, 4] | 4 左侧第一个更大元素为 6 |
32
+ | 8 | 2 | 2 入栈 | [ 7, 6, 4, 2] | 2 左侧第一个更大元素为 4 |
32
33
33
- 最终栈中元素为 $[ 7, 6, 4, 2] $。因为从栈顶(右端 )到栈底(左侧)元素的顺序为 $2, 4, 6, 7$,满足递增关系,所以这是一个单调递增栈 。
34
+ 最终,栈中元素为 $[ 7, 6, 4, 2] $。从栈顶(右 )到栈底(左)为 $2, 4, 6, 7$,满足递增,符合单调递增栈的定义 。
34
35
35
- 我们以上述过程第 5 步为例,所对应的图示过程为 :
36
+ 以第 5 步为例,图示如下 :
36
37
37
38
![ ] ( https://qcdn.itcharge.cn/images/20220107101219.png )
38
39
39
40
40
41
### 1.2 单调递减栈
41
42
42
- > ** 单调递减栈** :只有比栈顶元素大的元素才能直接进栈,否则需要先将栈中比当前元素大的元素出栈,再将当前元素入栈 。
43
+ > ** 单调递减栈** :每次新元素进栈时,只有当它比栈顶元素大时才能直接入栈;如果小于或等于栈顶元素,则需要先将栈中所有大于等于当前元素的元素依次弹出,直到栈顶元素小于当前元素或栈为空,再将新元素入栈 。
43
44
>
44
- > 这样就保证了:栈中保留的都是比当前入栈元素小的值,并且从栈顶到栈底的元素值是单调递减的 。
45
+ > 这样可以保证:栈中始终只保留比当前入栈元素小的值,并且从栈顶到栈底的元素是单调递减的 。
45
46
46
- 单调递减栈的入栈、出栈过程如下 :
47
+ 单调递减栈的操作流程如下 :
47
48
48
- - 假设当前进栈元素为 $x$,如果 $x$ 比栈顶元素大 ,则直接入栈。
49
- - 否则从栈顶开始遍历栈中元素,把大于 $x$ 或者等于 $x$ 的元素弹出栈 ,直到遇到一个小于 $x$ 的元素为止,然后再把 $x$ 压入栈中 。
49
+ - 假设当前待入栈元素为 $x$,如果 $x$ > 栈顶元素 ,则直接入栈。
50
+ - 否则,从栈顶开始,依次弹出所有大于等于 $x$ 的元素 ,直到遇到一个小于 $x$ 的元素或栈为空,然后将 $x$ 入栈 。
50
51
51
- 下面我们以数组 $[ 4, 3, 2, 5, 7, 4, 6, 8] $ 为例,模拟一下 「单调递减栈」的进栈、出栈过程。具体过程如下 :
52
+ 下面以数组 $[ 4, 3, 2, 5, 7, 4, 6, 8] $ 为例,演示 「单调递减栈」的入栈与出栈过程 :
52
53
53
- - 数组元素 :$[ 4, 3, 2, 5, 7, 4, 6, 8] $,遍历顺序为从左到右 。
54
+ - 数组 :$[ 4, 3, 2, 5, 7, 4, 6, 8] $,从左到右依次遍历 。
54
55
55
- | 第 i 步 | 待插入元素 | 操 作 | 结 果(左侧为栈底) | 作用 |
56
- | :----- : | :-------- : | - --------------------- | ------------------- | ------------------------------------- |
57
- | 1 | 4 | 4 入栈 | [ 4] | 元素 4 的左侧无比 4 小的元素 |
58
- | 2 | 3 | 4 出栈,3 入栈 | [ 3] | 元素 3 的左侧无比 3 小的元素 |
59
- | 3 | 2 | 3 出栈,2 入栈 | [ 2] | 元素 2 的左侧无比 2 小的元素 |
60
- | 4 | 5 | 5 入栈 | [ 2, 5] | 元素 5 的左侧第一个比 5 小的元素是:2 |
61
- | 5 | 7 | 7 入栈 | [ 2, 5, 7] | 元素 7 的左侧第一个比 7 小的元素是:5 |
62
- | 6 | 4 | 7 出栈,5 出栈,4 入栈 | [ 2, 4] | 元素 4 的左侧第一个比 4 小的元素是:2 |
63
- | 7 | 6 | 6 入栈 | [ 2, 4, 6] | 元素 6 的左侧第一个比 6 小的元素是:4 |
64
- | 8 | 8 | 8 入栈 | [ 2, 4, 6, 8] | 元素 8 的左侧第一个比 8 小的元素是:6 |
56
+ | 步骤 | 当前元素 | 操作 | 栈状态(左为栈底) | 说明 |
57
+ | :--: | :------: | --------------------- | ------------------- | ------------------------------------- |
58
+ | 1 | 4 | 4 入栈 | [ 4] | 4 左侧无更小元素 |
59
+ | 2 | 3 | 4 出栈,3 入栈 | [ 3] | 3 左侧无更小元素 |
60
+ | 3 | 2 | 3 出栈,2 入栈 | [ 2] | 2 左侧无更小元素 |
61
+ | 4 | 5 | 5 入栈 | [ 2, 5] | 5 左侧第一个更小元素为 2 |
62
+ | 5 | 7 | 7 入栈 | [ 2, 5, 7] | 7 左侧第一个更小元素为 5 |
63
+ | 6 | 4 | 7 出栈,5 出栈,4 入栈| [ 2, 4] | 4 左侧第一个更小元素为 2 |
64
+ | 7 | 6 | 6 入栈 | [ 2, 4, 6] | 6 左侧第一个更小元素为 4 |
65
+ | 8 | 8 | 8 入栈 | [ 2, 4, 6, 8] | 8 左侧第一个更小元素为 6 |
65
66
66
- 最终栈中元素为 $[ 2, 4, 6, 8] $。因为从栈顶(右端 )到栈底(左侧)元素的顺序为 $8, 6, 4, 2$,满足递减关系,所以这是一个单调递减栈 。
67
+ 最终,栈中元素为 $[ 2, 4, 6, 8] $。从栈顶(右 )到栈底(左)为 $8, 6, 4, 2$,满足递减,符合单调递减栈的定义 。
67
68
68
- 我们以上述过程第 6 步为例,所对应的图示过程为:
69
+ 以第 6 步为例,图示如下:
69
70
70
71
![ ] ( https://qcdn.itcharge.cn/images/20220107102446.png )
71
72
72
73
## 2. 单调栈适用场景
73
74
74
- 单调栈可以在时间复杂度为 $O(n)$ 的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素。
75
-
76
- 所以单调栈一般用于解决一下几种问题:
77
-
78
- - 寻找左侧第一个比当前元素大的元素。
79
- - 寻找左侧第一个比当前元素小的元素。
80
- - 寻找右侧第一个比当前元素大的元素。
81
- - 寻找右侧第一个比当前元素小的元素。
82
-
83
- 下面分别说一下这几种问题的求解方法。
84
-
85
- ### 2.1 寻找左侧第一个比当前元素大的元素
86
-
87
- - 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):
88
- - 一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。
89
- - 如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。
75
+ 单调栈常用于 $O(n)$ 时间复杂度内高效解决「最近更大/更小元素」类问题,主要包括以下四种典型场景:
90
76
77
+ - 查找左侧第一个比当前元素更大 / 更小的元素。
78
+ - 查找右侧第一个比当前元素更大 / 更小的元素。
91
79
92
- ### 2.2 寻找左侧第一个比当前元素小的元素
80
+ 具体解法如下:
93
81
94
- - 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):
95
- - 一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。
96
- - 如果插入时的栈为空,则说明左侧不存在比当前元素小的元素。
82
+ ### 2.1 查找左侧第一个比当前元素大的元素
97
83
84
+ - 从左到右遍历数组,维护单调递增栈(栈顶到栈底递增):
85
+ - 当前元素入栈时,栈顶元素即为其左侧第一个更大元素;
86
+ - 若栈为空,则左侧不存在更大元素。
98
87
99
- ### 2.3 寻找右侧第一个比当前元素大的元素
88
+ ### 2.2 查找左侧第一个比当前元素小的元素
100
89
101
- - 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增 ):
102
- - 一个元素右侧第一个比它大的元素就是将其「弹出单调递增栈」时即将插入的元素。
103
- - 如果该元素没有被弹出栈,则说明右侧不存在比当前元素大的元素 。
90
+ - 从左到右遍历数组,维护单调递减栈(栈顶到栈底递减 ):
91
+ - 当前元素入栈时,栈顶元素即为其左侧第一个更小元素;
92
+ - 若栈为空,则左侧不存在更小元素 。
104
93
105
- - 从右到左遍历元素,构造单调递增栈(从栈顶到栈底递增):
106
- - 一个元素右侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。
107
- - 如果插入时的栈为空,则说明右侧不存在比当前元素大的元素。
94
+ ### 2.3 查找右侧第一个比当前元素大的元素
108
95
96
+ - 从左到右遍历数组,维护单调递增栈:
97
+ - 当前元素将栈中比自己小的元素弹出,被弹出的元素的右侧第一个更大元素即为当前元素;
98
+ - 若某元素未被弹出,则右侧不存在更大元素。
99
+ - 或者,从右到左遍历,入栈时栈顶即为右侧第一个更大元素。
109
100
110
- ### 2.4 寻找右侧第一个比当前元素小的元素
101
+ ### 2.4 查找右侧第一个比当前元素小的元素
111
102
112
- - 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):
113
- - 一个元素右侧第一个比它小的元素就是将其「弹出单调递减栈」时即将插入的元素。
114
- - 如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素。
103
+ - 从左到右遍历数组,维护单调递减栈:
104
+ - 当前元素将栈中比自己大的元素弹出,被弹出的元素的右侧第一个更小元素即为当前元素;
105
+ - 若某元素未被弹出,则右侧不存在更小元素。
106
+ - 或者,从右到左遍历,入栈时栈顶即为右侧第一个更小元素。
115
107
116
- - 从右到左遍历元素,构造单调递减栈(从栈顶到栈底递减):
117
- - 一个元素右侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。
118
- - 如果插入时的栈为空,则说明右侧不存在比当前元素小的元素。
108
+ 上述四类问题可以归纳为以下通用规则:
119
109
120
-
121
- 上边的分类解法有点绕口,可以简单记为以下条规则:
122
-
123
- - 无论哪种题型,都建议从左到右遍历元素。
124
-
125
- - 查找 ** 「比当前元素大的元素」** 就用 ** 单调递增栈** ,查找 ** 「比当前元素小的元素」** 就用 ** 单调递减栈** 。
126
- - 从 ** 「左侧」** 查找就看 ** 「插入栈」** 时的栈顶元素,从 ** 「右侧」** 查找就看 ** 「弹出栈」** 时即将插入的元素。
110
+ - 查「更大」用单调递增栈,查「更小」用单调递减栈;
111
+ - 查「左侧」看元素入栈时的栈顶;
112
+ - 查「右侧」看元素出栈时触发它的当前元素;
113
+ - 遍历方向通常为从左到右(部分场景可从右到左)。
127
114
128
115
## 3. 单调栈模板
129
116
134
121
``` python
135
122
def monotoneIncreasingStack (nums ):
136
123
stack = []
137
- for num in nums:
124
+ left
125
+ for i, num in enumerate (nums):
138
126
while stack and num >= stack[- 1 ]:
139
127
stack.pop()
128
+ if stack:
129
+
140
130
stack.append(num)
141
131
```
142
132
@@ -151,9 +141,11 @@ def monotoneDecreasingStack(nums):
151
141
stack.append(num)
152
142
```
153
143
154
- ## 4. 单调栈的应用
144
+ 等号的去留(>= 或 >,<= 或 <)决定是否保留相等元素,按题意调整。
145
+
146
+ ## 4. 单调栈的经典应用
155
147
156
- ### 4.1 下一个更大元素 I
148
+ ### 4.1 经典例题: 下一个更大元素 I
157
149
158
150
#### 4.1.1 题目链接
159
151
0 commit comments