栈专题

203 阅读5分钟

1. 栈

栈是一种受限的数据结构,只允许数据从一个方向插入或删除,这个方向也叫栈顶,对应的另一边不能进行操作的方向叫栈底
栈的特征是 LIFO(Last in, First out,后进先出)

2. 栈

1. 常用操作

通常在 Go 里直接拿切片当作栈,数组末端是栈顶,即在数组末尾进行插入删除操作
image.png

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. 有效的括号

遇到左括号入栈相应的右括号;
遇到右括号判断是否和栈顶元素一致
三种非法情况:

  1. 匹配完了栈还有剩
  2. 还没匹配完栈空了
  3. 栈顶元素和栈顶匹配不上
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这种题目
使用单调栈要明确以下几个问题:

  1. 单调栈里存放的元素是什么?

单调栈里通常存放的是数组下标,因为数组的值可以通过下标获取,反过来就不行

  1. 单调栈里的元素,从栈底到栈顶,是递增的(单调递增栈)还是递减的(单调递减栈)?

如果选择某种单调性,在入栈操作时,需要一直维护这种单调性

单调递增栈为例,操作规则如下:

  1. 当前遍历元素T[i]大于栈顶元素,直接入栈
  2. 当前遍历元素T[i]等于栈顶元素,直接入栈
  3. 当前遍历元素T[i]小于栈顶元素,需要保持单调性,不断出栈栈顶元素,直到栈空或者栈顶元素大于等于T[i]


和直觉有点相反,当要求下一个大于xxx的数,那遇到大于xxx的情况时,应该出栈结算,即需要使用单调递减

如果是求值,涉及的值通常有两个:

  1. 不满足单调栈的值
  2. 当前单调栈栈顶值

如果是求面积,通常是三个:

  1. 不满足单调栈的值
  2. 当前单调栈栈顶值
  3. 当前单调栈栈顶出栈后,栈顶的值    

单调递增栈模板:

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. 接雨水

单调递减栈,遇到更大的结算

涉及三个值:  

  1. 不满足单调性的 v         右边界
  2. 栈顶元素                 底
  3. 栈顶元素出栈后的栈顶元素  左边界

面积 = 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
}