1. 栈
栈是一种受限的数据结构,只允许数据从一个方向插入或删除,这个方向也叫栈顶,对应的另一边不能进行操作的方向叫栈底
栈的特征是 LIFO(Last in, First out,后进先出)
2. 栈
1. 常用操作
通常在 Go 里直接拿切片当作栈,数组末端是栈顶,即在数组末尾进行插入删除操作
stack := make([]int, 0)
进栈 push
stack = append(stack, val)
出栈 pop
stack = stack[:len(stack)-1]
访问栈顶元素 top
top := stack[len(stack)-1]
栈是否空 isEmpty
isEmpty := len(stack) == 0
由于是在数组末尾进行操作,借助切片,时间复杂度都是 O(1)
2. 常见题目
常见应用有:
- 函数调用栈
- 浏览器前进后退
- 括号匹配
- 逆波兰表达式求值
20. 有效的括号
遇到左括号入栈相应的右括号;
遇到右括号判断是否和栈顶元素一致
三种非法情况:
- 匹配完了栈还有剩
- 还没匹配完栈空了
- 栈顶元素和栈顶匹配不上
func isValid(s string) bool {
stack := []byte{}
for i := range s {
if s[i] == '{' {
stack = append(stack, '}')
} else if s[i] == '[' {
stack = append(stack, ']')
} else if s[i] == '(' {
stack = append(stack, ')')
} else if len(stack) == 0 {
return false
} else {
top := stack[len(stack)-1]
if top != s[i] {
return false
}
stack = stack[:len(stack)-1]
}
}
return len(stack) == 0
}
150. 逆波兰表达式求值
当遇到数字,入栈;
当遇到符号,出栈两个栈顶数字,按符号计算后再次入栈
func evalRPN(tokens []string) int {
stack := []int{}
for _, str := range tokens {
val, err := strconv.Atoi(str)
if err != nil {
num2 := stack[len(stack)-1]
num1 := stack[len(stack)-2]
stack = stack[:len(stack)-2]
switch str{
case "+":
stack = append(stack, num1+num2)
case "-":
stack = append(stack, num1-num2)
case "*":
stack = append(stack, num1*num2)
case "/":
stack = append(stack, num1/num2)
}
} else {
stack = append(stack, val)
}
}
return stack[0]
}
155. 最小栈
需要额外构造一个栈,栈中存最小值
这个栈 push 时和普通栈有区别:push 时比较栈顶元素和要 push 的值,谁小把谁 push 进栈
type MinStack struct {
stack []int
minStack []int
size int
}
func Constructor() MinStack {
return MinStack{
stack: make([]int, 0),
minStack: make([]int, 0),
size: 0,
}
}
func (this *MinStack) Push(val int) {
this.stack = append(this.stack, val)
if this.size != 0 && val > this.GetMin() {
this.minStack = append(this.minStack, this.GetMin())
} else {
this.minStack = append(this.minStack, val)
}
this.size++
}
func (this *MinStack) Pop() {
this.stack = this.stack[:this.size-1]
this.minStack = this.minStack[:this.size-1]
this.size--
}
func (this *MinStack) Top() int {
return this.stack[this.size-1]
}
func (this *MinStack) GetMin() int {
return this.minStack[this.size-1]
}
394. 字符串解码
当遇到数字,尝试把数字解析出来入数字栈
当遇到左括号,代表进入新一层,将前面的临时字符串先入字符串栈存起来,重置临时字符串
当遇到右括号,计算该层字符串,该层字符串 = 当前临时字符串 * 数字栈出栈第一个值,再和前面的临时字符串,即字符串栈出栈第一个元素拼接起来作为新的临时字符串
func decodeString(s string) string {
str := ""
digStack := []int{}
strStack := []string{}
for i := 0; i < len(s); i++ {
if s[i] >= '0' && s[i] <= '9' {
l := i
for s[i] >= '0' && s[i] <= '9' {
i++
}
num, _ := strconv.Atoi(s[l:i])
digStack = append(digStack, num)
i--
} else if s[i] == '[' {
strStack = append(strStack, str)
str = ""
} else if s[i] == ']' { // 结账
num := digStack[len(digStack)-1]
digStack = digStack[:len(digStack)-1]
str = strings.Repeat(str, num)
str = strStack[len(strStack)-1] + str
strStack = strStack[:len(strStack)-1]
} else {
str += string(s[i])
}
}
return str
}
或者借助函数调用栈实现
每次遇到 '[',开启新一轮递归,并计算这一轮字符串(因为开启的递归无法知道重复的数字,所以只有调用方处理)
每次遇到 ']',结束这一层的递归
func decodeString(s string) string {
ans, _ := helper(s, 0)
return ans
}
func helper(s string, i int) (string, int) {
num := 0
ans := ""
for i < len(s) {
if s[i] >= '0' && s[i] <= '9' {
num = num*10 + int(s[i]-'0')
} else if s[i] == '['{
str, j := helper(s, i+1)
ans += strings.Repeat(str, num)
num = 0
i = j
} else if s[i] == ']' {
return ans, i
} else {
ans += string(s[i])
}
i++
}
return ans, i
}
946. 验证栈序列
模拟过程
pushed元素入栈
当前栈顶元素和 popped 当前值相等,就出栈
func validateStackSequences(pushed []int, popped []int) bool {
i := 0
stack := []int{}
for _, v := range pushed {
stack = append(stack, v)
for len(stack) > 0 && stack[len(stack)-1] == popped[i] {
stack = stack[:len(stack)-1]
i++
}
}
return len(stack) == 0
}
1381. 设计一个支持增量操作的栈
type CustomStack struct {
stack []int
maxSize int
}
func Constructor(maxSize int) CustomStack {
return CustomStack{
maxSize: maxSize,
}
}
func (this *CustomStack) Push(x int) {
if len(this.stack) == this.maxSize {
return
}
this.stack = append(this.stack, x)
}
func (this *CustomStack) Pop() int {
if len(this.stack) == 0 {
return -1
}
top := this.stack[len(this.stack)-1]
this.stack = this.stack[:len(this.stack)-1]
return top
}
func (this *CustomStack) Increment(k int, val int) {
if k > len(this.stack) {
k = len(this.stack)
}
for i := 0; i < k; i++ {
this.stack[i] = this.stack[i] + val
}
}
/**
* Your CustomStack object will be instantiated and called as such:
* obj := Constructor(maxSize);
* obj.Push(x);
* param_2 := obj.Pop();
* obj.Increment(k,val);
*/
3. 单调栈
1. 概念及模板
单调栈要解决的问题场景是:求解下一个大于xxx或下一个小于xxx这种题目
使用单调栈要明确以下几个问题:
- 单调栈里存放的元素是什么?
单调栈里通常存放的是数组下标,因为数组的值可以通过下标获取,反过来就不行
- 单调栈里的元素,从栈底到栈顶,是递增的(单调递增栈)还是递减的(单调递减栈)?
如果选择某种单调性,在入栈操作时,需要一直维护这种单调性
以单调递增栈为例,操作规则如下:
- 当前遍历元素T[i]大于栈顶元素,直接入栈
- 当前遍历元素T[i]等于栈顶元素,直接入栈
- 当前遍历元素T[i]小于栈顶元素,需要保持单调性,不断出栈栈顶元素,直到栈空或者栈顶元素大于等于T[i]
和直觉有点相反,当要求下一个大于xxx的数,那遇到大于xxx的情况时,应该出栈结算,即需要使用单调递减栈
如果是求值,涉及的值通常有两个:
- 不满足单调栈的值
- 当前单调栈栈顶值
如果是求面积,通常是三个:
- 不满足单调栈的值
- 当前单调栈栈顶值
- 当前单调栈栈顶出栈后,栈顶的值
单调递增栈模板:
stack := []int{}
for i, v := range num {
// 可能需要多次 pop,所以是 for
// 当栈不空,且当前遍历元素破坏了栈的单调性
// 递增栈和递减栈的区别在 v > 后面的数还是 v < 后面的数
for len(stack) != 0 && v > num[stack[len(stack)-1]] {
// pop
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
// 做操作
}
stack = append(stack, i)
}
2. 常见题目
739. 每日温度
单调递减栈
func dailyTemperatures(num []int) []int {
ans := make([]int, len(num))
stack := []int{}
for i, v := range num {
for len(stack) != 0 && v > num[stack[len(stack)-1]] {
// pop
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
ans[top] = i - top
}
stack = append(stack, i)
}
return ans
}
42. 接雨水
单调递减栈,遇到更大的结算
涉及三个值:
- 不满足单调性的 v 右边界
- 栈顶元素 底
- 栈顶元素出栈后的栈顶元素 左边界
面积 = min((右-底),(左-底))*(右-左)
func trap(height []int) int {
stack := []int{}
ans := 0
for i, v := range height {
for len(stack) != 0 && v > height[stack[len(stack)-1]] {
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
// 相同高度,接不住雨水
for len(stack) != 0 && height[top] == height[stack[len(stack)-1]] {
stack = stack[:len(stack)-1]
}
if len(stack) != 0 {
l := stack[len(stack)-1]
h := min(v-height[top], height[l]-height[top])
w := i-l-1
ans += h*w
}
}
stack = append(stack, i)
}
return ans
}
func min(a, b int) int {
if a > b {
return b
}
return a
}