1. 队列、栈、堆简介

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表

image.png
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
image.png

注意:队列与栈的添加与操作操作皆为o(1),查找是o(n)。

我们完全可以把堆(以下全都默认为最大堆)看成一棵完全二叉树,但是位于堆顶的元素总是整棵树的最大值,每个子节点的值都比父节点小,由于堆要时刻保持这样的规则特性,所以一旦堆里面的数据发生变化,我们必须对堆重新进行一次构建。

2. 如何实现队列、栈、堆

队列与栈可以由多种基本数据结构实现。很多语言内置语法已实现常用队列与栈。
为了方便使用,笔者使用go语言数组实现了各种栈与队列。(go语言还未有队列与栈的内置实现

注意:笔者实现的队列与栈未处理下标异常情况,仅供练习与刷题使用,勿用于生产环境。

基本队列:

  1. type MyQueue struct {
  2. queue []int
  3. }
  4. //初始化队列
  5. func NewQueue() MyQueue{
  6. return MyQueue{}
  7. }
  8. //返回队列长度
  9. func (m *MyQueue) Len()int{
  10. return len(m.queue)
  11. }
  12. //查看队列头元素
  13. func (m *MyQueue) Peek()int{
  14. return m.queue[0]
  15. }
  16. //元素出队列
  17. func (m *MyQueue) Poll()int{
  18. top := m.Peek()
  19. m.queue = m.queue[1:]
  20. return top
  21. }
  22. //元素进队列
  23. func (m *MyQueue) Add(x int){
  24. m.queue = append(m.queue,x)
  25. }

双端队列:

  1. type MyDeque struct {
  2. deque []int
  3. }
  4. //初始化双端队列
  5. func NewDeque() MyDeque{
  6. return MyDeque{}
  7. }
  8. //返回双端队列长度
  9. func (m *MyDeque) Len()int{
  10. return len(m.deque)
  11. }
  12. //将元素插入到队列头部
  13. func (m *MyDeque) InsertFront(x int){
  14. m.deque = append([]int{x},m.deque...)
  15. }
  16. //将元素追加到队列尾部
  17. func (m *MyDeque) InsertLast(x int){
  18. m.deque = append(m.deque,x)
  19. }
  20. //从头部删除元素
  21. func (m *MyDeque) DeleteFront()int{
  22. num := m.deque[0]
  23. m.deque = m.deque[1:]
  24. return num
  25. }
  26. //从尾部删除元素
  27. func (m *MyDeque) DeleteLast()int{
  28. num := m.deque[m.Len()-1]
  29. m.deque = m.deque[:m.Len()-1]
  30. return num
  31. }
  32. //获取头部元素
  33. func (m *MyDeque) GetFront()int{
  34. return m.deque[0]
  35. }
  36. //获取尾部元素
  37. func (m *MyDeque) GetRear()int{
  38. return m.deque[len(m.deque)-1]
  39. }

单调队列:

  1. type MyQueue struct {
  2. queue []int
  3. }
  4. //初始化队列
  5. func NewQueue() MyQueue{
  6. return MyQueue{}
  7. }
  8. //返回队列长度
  9. func (m *MyQueue) Len()int{
  10. return len(m.queue)
  11. }
  12. //查看队列头元素
  13. func (m *MyQueue) Peek()int{
  14. return m.queue[0]
  15. }
  16. //元素出队列
  17. func (m *MyQueue) Poll()int{
  18. top := m.Peek()
  19. m.queue = m.queue[1:]
  20. return top
  21. }
  22. //元素进队列
  23. func (m *MyQueue) Add(x int){
  24. //若队尾元素大于待添加元素,则移除队尾元素,保证队列是单调递增的
  25. for m.Len() > 0 && m.queue[m.Len()-1] > x{
  26. m.queue = m.queue[:m.Len()-1]
  27. }
  28. m.queue = append(m.queue,x)
  29. }

基本栈:

  1. type MyStack struct {
  2. stack []int
  3. }
  4. //初始化栈
  5. func NewStack() MyStack{
  6. return MyStack{}
  7. }
  8. //返回栈长度
  9. func (m *MyStack) Len()int{
  10. return len(m.stack)
  11. }
  12. //查看栈顶元素
  13. func (m *MyStack) Top()int{
  14. return m.stack[len(m.stack)-1]
  15. }
  16. //弹出栈顶元素
  17. func (m *MyStack) Pop()int{
  18. pop := m.Top()
  19. m.stack = m.stack[:len(m.stack)-1]
  20. return pop
  21. }
  22. //往栈中压入新元素
  23. func (m *MyStack) Push(x int){
  24. m.stack = append(m.stack,x)
  25. }

大根堆(用数组方式实现):

  1. type Heap struct{
  2. heap []int
  3. }
  4. func Init(nums []int)*Heap{
  5. h := &Heap{
  6. heap : nums,
  7. }
  8. h.init()
  9. return h
  10. }
  11. func (h *Heap)init(){
  12. //初始化时不断的下沉
  13. n := h.len()
  14. for i := n/2-1;i >= 0;i--{
  15. h.down(i)
  16. }
  17. }
  18. //返回堆的长度
  19. func(h *Heap)len()int{
  20. return len(h.heap)
  21. }
  22. //交换元素
  23. func(h *Heap)swap(i,j int){
  24. h.heap[i],h.heap[j] = h.heap[j],h.heap[i]
  25. }
  26. //比较大小
  27. func(h *Heap)more(i,j int)bool{
  28. return h.heap[i] > h.heap[j]
  29. }
  30. //上升
  31. func (h *Heap)up(i int){
  32. for {
  33. p := (i-1) / 2 //父节点
  34. if p == i || h.more(p,i){
  35. break
  36. }
  37. h.swap(p,i)
  38. i = p
  39. }
  40. }
  41. //添加元素时,会首先把元素添加到末尾,然后去和父元素进行比较直到每个树的根元素都大于子树的数。
  42. func (h *Heap)Push(v int){
  43. h.heap = append(h.heap,v)
  44. h.up(h.len()-1)
  45. }
  46. //下沉
  47. func (h *Heap)down(i int){
  48. for {
  49. left := 2*i+1 //左子节点
  50. //越界判断
  51. if left < 0 || left >= h.len(){
  52. break
  53. }
  54. j := left
  55. //下沉时要去和数值大的子树进行比较。
  56. if right := left + 1;right < h.len() && h.more(right,left){
  57. j = right
  58. }
  59. if h.more(i,j){
  60. break
  61. }
  62. h.swap(i,j)
  63. i = j
  64. }
  65. }
  66. //移除元素
  67. func (h *Heap)Remove(i int)(int,bool){
  68. if i < 0 || i >= h.len(){
  69. return 0,false
  70. }
  71. //移除元素时,会先将移除元素移动到最末端的位置,然后再重新调整位置。
  72. last := h.len()-1
  73. h.swap(last,i)
  74. x := h.heap[last]
  75. h.heap = h.heap[0:last]
  76. if i == 0 || (h.heap[i] < h.heap[(i-1)/2]){
  77. h.down(i)
  78. }else{
  79. h.up(i)
  80. }
  81. return x,true
  82. }
  83. func (h *Heap)Pop()(int,bool){
  84. return h.Remove(0)
  85. }

大根堆(实现contain/heap接口):

  1. type Interface interface {
  2. sort.Interface
  3. Push(x interface{}) // add x as element Len()
  4. Pop() interface{} // remove and return element Len() - 1.
  5. }
  6. func (h IntHeap) Len() int { return len(h) }
  7. // Less 为了实现大根堆,Less在大于时返回小于
  8. func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] }
  9. func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

3. 队列经典题目必须 掌握


232. 用栈实现队列简单

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

示例:
输入:
[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

提示:
1 <= x <= 9
最多调用 100 次 push、pop、peek 和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)

题解概要:

1.使用两个栈,一个栈进,另一个栈出。
2.当调用pop或peek操作时,将入栈元素弹出并压入到出栈中,再将出栈栈顶返回即可。
动画:
点击查看【bilibili】
代码:

  1. type MyQueue struct {
  2. inStack []int //入栈
  3. outStack []int //出栈
  4. }
  5. func Constructor() MyQueue {
  6. return MyQueue{}
  7. }
  8. func (q *MyQueue) Push(x int) {
  9. q.inStack = append(q.inStack,x)
  10. }
  11. func (q *MyQueue) in2out() {
  12. for len(q.inStack) > 0 {
  13. last := len(q.inStack) -1
  14. q.outStack = append(q.outStack,q.inStack[last])
  15. q.inStack = q.inStack[:last]
  16. }
  17. }
  18. func (q *MyQueue) Pop() int {
  19. if len(q.outStack) < 1{
  20. q.in2out()
  21. }
  22. num := q.outStack[len(q.outStack)-1]
  23. q.outStack = q.outStack[:len(q.outStack)-1]
  24. return num
  25. }
  26. func (q *MyQueue) Peek() int {
  27. if len(q.outStack) < 1{
  28. q.in2out()
  29. }
  30. return q.outStack[len(q.outStack)-1]
  31. }
  32. func (q *MyQueue) Empty() bool {
  33. return len(q.inStack) == 0 && len(q.outStack) == 0
  34. }

剑指 Offer 59 - I. 滑动窗口的最大值 困难

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

滑动窗口的位置 最大值
———————- ——-
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

题解概要:

使用单调递减队列记录每个滑窗的最大值。
图解:
队列、栈、堆 - 图3
代码:

  1. type Deque struct{
  2. Queue []int //单调递减队列
  3. }
  4. func Constructor() Deque {
  5. return Deque{}
  6. }
  7. // 单调递减队列的push方法,保证队列是递减的
  8. func (d *Deque) Push(val int){
  9. for len(d.Queue) > 0 && d.Queue[len(d.Queue)-1] < val{
  10. d.Queue = d.Queue[:len(d.Queue)-1]
  11. }
  12. d.Queue = append(d.Queue,val)
  13. }
  14. //返回单调队列最大值
  15. func (d *Deque) Max() int{
  16. if len(d.Queue) <= 0{
  17. return -1
  18. }
  19. return d.Queue[0]
  20. }
  21. // 判断val是否在队列头部
  22. func (d *Deque) Remove(val int) {
  23. if len(d.Queue) <= 0{
  24. return
  25. }
  26. if d.Queue[0] == val{
  27. d.Queue = d.Queue[1:]
  28. }
  29. }
  30. func maxSlidingWindow(nums []int, k int) []int {
  31. c := Constructor() //初始化单调队列
  32. res := []int{} //存放结果集
  33. l,r := 0,0 //左指针与右指针
  34. for r < len(nums){
  35. c.Push(nums[r]) //入队列
  36. //出滑窗
  37. for r-l+1 > k{
  38. c.Remove(nums[l])
  39. l +=1
  40. }
  41. //更新结果集
  42. if r-l+1 == k{
  43. res = append(res,c.Max())
  44. }
  45. r += 1
  46. }
  47. return res
  48. }

225. 用队列实现栈 简单

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:
输入:
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

题解概要:

  1. 需借助辅助队列。每次添加元素时,先将元素添加到辅助队列,再将主队列元素依次加入到辅助队列。最后将辅助队列赋值给主队列,并将辅助队列设置为空队列。
  2. 这就保证主队列元素为先入后出

动画如下:
队列、栈、堆 - 图4

动画:
点击查看【bilibili】
代码:

  1. type MyStack struct {
  2. q1 []int //主队列
  3. q2 []int //辅助队列
  4. }
  5. /** Initialize your data structure here. */
  6. func Constructor() MyStack {
  7. return MyStack{}
  8. }
  9. /** Push element x onto stack. */
  10. func (this *MyStack) Push(x int) {
  11. this.q2 = append(this.q2,x)
  12. this.q2 = append(this.q2,this.q1...)
  13. this.q1 = this.q2
  14. this.q2 = []int{}
  15. }
  16. /** Removes the element on top of the stack and returns that element. */
  17. func (this *MyStack) Pop() int {
  18. pop := this.Top()
  19. this.q1 = this.q1[1:]
  20. return pop
  21. }
  22. /** Get the top element. */
  23. func (this *MyStack) Top() int {
  24. return this.q1[0]
  25. }
  26. /** Returns whether the stack is empty. */
  27. func (this *MyStack) Empty() bool {
  28. return len(this.q1) == 0
  29. }

剑指 Offer 59 - II. 队列的最大值 中等

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:
输入:
[“MaxQueue”,”push_back”,”push_back”,”max_value”,”pop_front”,”max_value”]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

示例 2:
输入:
[“MaxQueue”,”pop_front”,”max_value”]
[[],[],[]]
输出: [null,-1,-1]

限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5

题解概要:

1.使用两个队列,一个队列维护元素入队顺序,另一个单调递减队列维护队列最大值。
图解:
队列、栈、堆 - 图5
队列、栈、堆 - 图6
队列、栈、堆 - 图7
队列、栈、堆 - 图8
代码:

  1. type MaxQueue struct {
  2. queue []int //主队列
  3. deque []int //双端队列
  4. }
  5. func Constructor() MaxQueue {
  6. return MaxQueue{}
  7. }
  8. func (this *MaxQueue) Max_value() int {
  9. if len(this.deque) < 1{
  10. return -1
  11. }
  12. return this.deque[0]
  13. }
  14. func (this *MaxQueue) Push_back(value int) {
  15. this.queue = append(this.queue,value)
  16. for len(this.deque) > 0 && this.deque[len(this.deque)-1] < value{
  17. this.deque = this.deque[:len(this.deque)-1]
  18. }//保证队列的单调性
  19. this.deque = append(this.deque,value)
  20. }
  21. func (this *MaxQueue) Pop_front() int {
  22. if len(this.queue) < 1{
  23. return -1
  24. }
  25. val := this.queue[0]
  26. this.queue = this.queue[1:]
  27. if val == this.deque[0] {
  28. this.deque = this.deque[1:]
  29. }
  30. return val
  31. }

862. 和至少为 K 的最短子数组 困难

给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。

子数组 是数组中 连续 的一部分。

示例 1:
输入:nums = [1], k = 1
输出:1

示例 2:
输入:nums = [1,2], k = 4
输出:-1

示例 3:
输入:nums = [2,-1,2], k = 3
输出:3

提示:
1 <= nums.length <= 105
-105 <= nums[i] <= 105
1 <= k <= 109

题解概要:

1.使用前缀和与单调队列。
图解:
待补充图解。

代码:

  1. func shortestSubarray(nums []int, k int) int {
  2. sum := make([]int,len(nums)+1) //前缀和
  3. queue := []int{0} //单调队列
  4. for i,v := range nums{
  5. sum[i+1] = sum[i] + v
  6. }
  7. res := math.MaxInt32
  8. for i := 1; i < len(sum);i++ {
  9. //因为k是正数,如果sum[i-1] >= sum[i]了,就一定不符合条件。所以,要从数组中移除。也就是说,队列是单调自增队列。
  10. for len(queue) > 0 && sum[queue[len(queue)-1]] >= sum[i]{
  11. queue = queue[:len(queue)-1]
  12. }
  13. queue = append(queue,i)
  14. //需满足条件sum[i] -queue[0] >= k,但是queue[0]此时有可能为空,应转换不等式。所以sum[i] -k 应该大于等于 queue[0]
  15. diff := sum[i] - k
  16. for len(queue) > 0 && diff >= sum[queue[0]]{
  17. res = min(res,i-queue[0])
  18. queue = queue[1:]
  19. }
  20. }
  21. if res == math.MaxInt32 {
  22. return -1
  23. }
  24. return res
  25. }
  26. func min(num1 int,num2 int)int{
  27. if num2 < num1 {
  28. return num2
  29. }
  30. return num1
  31. }

1438. 绝对差不超过限制的最长连续子数组 中等

给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。

如果不存在满足条件的子数组,则返回 0 。

示例 1:
输入:nums = [8,2,4,7], limit = 4
输出:2
解释:所有子数组如下:
[8] 最大绝对差 |8-8| = 0 <= 4.
[8,2] 最大绝对差 |8-2| = 6 > 4.
[8,2,4] 最大绝对差 |8-2| = 6 > 4.
[8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
[2] 最大绝对差 |2-2| = 0 <= 4.
[2,4] 最大绝对差 |2-4| = 2 <= 4.
[2,4,7] 最大绝对差 |2-7| = 5 > 4.
[4] 最大绝对差 |4-4| = 0 <= 4.
[4,7] 最大绝对差 |4-7| = 3 <= 4.
[7] 最大绝对差 |7-7| = 0 <= 4.
因此,满足题意的最长子数组的长度为 2 。

示例 2:
输入:nums = [10,1,2,4,7,2], limit = 5
输出:4
解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。

示例 3:
输入:nums = [4,2,2,2,4,4,2,2], limit = 0
输出:3

提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
0 <= limit <= 10^9

题解概要:

1.使用两个队列,一个单调递增队列,一个单调递减队列。
2.再使用滑动窗口模板更新子数组长度的最大值即可。

注意:这道题最简单的方法是使用有序的数据结构,这样就可以在o(1)时间内获得最大值与最小值,从而算出最大绝 对差。
image.png
引入大佬题解:https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/solution/he-gua-de-shu-ju-jie-gou-hua-dong-chuang-v46j/

代码:

  1. type Queue struct{
  2. minQueue []int //单调递减队列
  3. maxQueue []int //单调递增队列
  4. }
  5. func New() Queue{
  6. return Queue{}
  7. }
  8. func (q *Queue) pushBack(val int){
  9. for len(q.maxQueue) > 0 && q.maxQueue[len(q.maxQueue)-1] < val{
  10. q.maxQueue = q.maxQueue[:len(q.maxQueue)-1]
  11. }//单调递增队列入队列
  12. for len(q.minQueue) > 0 && q.minQueue[len(q.minQueue)-1] > val{
  13. q.minQueue = q.minQueue[:len(q.minQueue)-1]
  14. }//单调递减队列入队列
  15. q.maxQueue = append(q.maxQueue,val)
  16. q.minQueue = append(q.minQueue,val)
  17. }
  18. func (q *Queue) update(val int){
  19. //滑窗收缩时,更新两个队列的数据。
  20. if q.maxQueue[0] == val{
  21. q.maxQueue = q.maxQueue[1:]
  22. }
  23. if q.minQueue[0] == val{
  24. q.minQueue = q.minQueue[1:]
  25. }
  26. }
  27. //获取绝对差
  28. func (q *Queue) getNum()int{
  29. return abs(q.maxQueue[0]-q.minQueue[0])
  30. }
  31. //封装绝对值方法
  32. func abs(val int)int{
  33. if val < 0{
  34. return -val
  35. }
  36. return val
  37. }
  38. //封装两数比较大小方法
  39. func max(num1 int,num2 int)int{
  40. if num1 > num2{
  41. return num1
  42. }
  43. return num2
  44. }
  45. func longestSubarray(nums []int, limit int) int {
  46. l,r := 0,0
  47. res := 0
  48. q := New()
  49. for r < len(nums){
  50. q.pushBack(nums[r])
  51. //不满足条件时,收缩窗口。
  52. for q.getNum() > limit{
  53. q.update(nums[l])
  54. l += 1
  55. }
  56. res = max(res,r-l+1)
  57. r += 1
  58. }
  59. return res
  60. }

总结

1.题目中考察队列较少,多为单调递增或递减队列。
2.求xxx的最大值或xxx的最小值,可以尝试使用队列,这样就能保证整个算法在o(n)时间内,只是牺牲入队列的一些性能。


4. 栈-括号题目必须 掌握


20. 有效的括号简单

给定一个只包括 ‘(‘,’)’,’{‘,’}’,’[‘,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1:
输入:s = “()”
输出:true

示例 2:
输入:s = “()[]{}”
输出:true

示例 3:
输入:s = “(]”
输出:false

示例 4:
输入:s = “([)]”
输出:false

示例 5:
输入:s = “{[]}”
输出:true

提示:
1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成

题解概要:

1.创一个存放字符的栈。
2.遇到(、[、{则将对应的括号入栈。(方便后面计算)
3.遇到)、]、}时,判断是否与栈顶元素匹配。若匹配则将栈顶移除,不匹配return false。
4.最后判断栈是否为空,若为空说明是有效的。
图解:
队列、栈、堆 - 图10
代码:

  1. func isValid(s string) bool {
  2. stack := []byte{}
  3. for i,_ := range s{
  4. if s[i] == '('{
  5. stack = append(stack,')')
  6. }else if s[i] == '{'{
  7. stack = append(stack,'}')
  8. }else if s[i] == '['{
  9. stack = append(stack,']')
  10. }else if len(stack) == 0 || stack[len(stack)-1] != s[i]{
  11. return false
  12. }else{
  13. stack = stack[:len(stack)-1]
  14. }
  15. }
  16. return len(stack) == 0
  17. }

1190. 反转每对括号间的子串 中等

给出一个字符串 s(仅含有小写英文字母和括号)。

请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。

示例 1:
输入:s = “(abcd)”
输出:”dcba”

示例 2:
输入:s = “(u(love)i)”
输出:”iloveu”
解释:先反转子字符串 “love” ,然后反转整个字符串。

示例 3:
输入:s = “(ed(et(oc))el)”
输出:”leetcode”
解释:先反转子字符串 “oc” ,接着反转 “etco” ,然后反转整个字符串。

示例 4:
输入:s = “a(bcdefghijkl(mno)p)q”
输出:”apmnolkjihgfedcbq”

提示:

  • 0 <= s.length <= 2000
  • s 中只有小写英文字母和括号
  • 题目测试用例确保所有括号都是成对出现的

题解概要:

1.创建一个放字符串的栈, 以及一个保存当前字符的变量
2.遇到 ( 就将当前的字符串推入栈, 并将当前字符串其设置为空
3.遇到 ) 就将当前的字符串反转, 然后与栈的顶部元素合并, 将栈的顶部元素弹出
4.遇到普通的字符就将其添加到当前字符串的尾部
5.遍历结束返回字符串
动画:
点击查看【bilibili】
代码:

  1. func reverseParentheses(s string) string {
  2. stack := [][]byte{}
  3. str := []byte{}
  4. for i,_ := range s{
  5. if s[i] == '('{
  6. stack = append(stack,str)
  7. str = []byte{}
  8. }else if s[i] == ')'{
  9. str = reverse(str)
  10. str = append(stack[len(stack)-1],str...)
  11. stack = stack[:len(stack)-1]
  12. }else{
  13. str = append(str,s[i])
  14. }
  15. }
  16. return string(str)
  17. }
  18. func reverse(str []byte)[]byte{
  19. i := 0
  20. j := len(str)-1
  21. for i < j{
  22. temp := str[i]
  23. str[i] = str[j]
  24. str[j] = temp
  25. i++
  26. j--
  27. }
  28. return str
  29. }

394. 字符串解码 中等

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例 1:
输入:s = “3[a]2[bc]”
输出:”aaabcbc”

示例 2:
输入:s = “3[a2[c]]”
输出:”accaccacc”

示例 3:
输入:s = “2[abc]3[cd]ef”
输出:”abcabccdcdcdef”

示例 4:
输入:s = “abc3[cd]xyz”
输出:”abccdcdcdxyz”

题解概要:

1.构建辅助栈 stack, 遍历字符串 s 中每个字符 c;
当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算;
当 c 为字母时,在 res 尾部添加 c;
当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置空置 00:
记录此 [ 前的临时结果 res 至栈,用于发现对应] 后的拼接操作;
记录此 [ 前的倍数 multi 至栈,用于发现对应 ] 后,获取 multi × […] 字符串。
进入到新 [ 后,res 和 multi 重新记录。
当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + cur_multi * res,其中:+

last_res是上个 [ 到当前 [ 的字符串,例如 “3[a2[c]]” 中的 a;
cur_multi是当前 [ 到 ] 内字符串的重复倍数,例如 “3[a2[c]]” 中的 2。
2.返回字符串 res。

注意:
可将两个栈合为一个栈。
笔者主用go刷题,在go中,字符是 []byte数组。一个栈可读性较差,故使用两个栈。

代码:

  1. import "strconv"
  2. func decodeString(s string) string {
  3. numStack := []int{}
  4. strStack := [][]byte{}
  5. num := 0
  6. result := []byte{}
  7. for i,_ := range s{
  8. if '0' <= s[i] && s[i] <= '9'{
  9. n,_ := strconv.Atoi(string(s[i]))
  10. num = num*10 + n
  11. }else if s[i] == '['{
  12. numStack = append(numStack,num)
  13. num = 0
  14. strStack = append(strStack,result)
  15. result = []byte{}
  16. }else if s[i] == ']'{
  17. count := numStack[len(numStack)-1]
  18. numStack = numStack[:len(numStack)-1]
  19. str := strStack[len(strStack)-1]
  20. strStack = strStack[:len(strStack)-1]
  21. temp := []byte{}
  22. for i:= 0;i<count;i++{
  23. temp = append(temp,result...)
  24. }
  25. result = append(str,temp...)
  26. }else{
  27. result = append(result,s[i])
  28. }
  29. }
  30. return string(result)
  31. }

678. 有效的括号字符串 中等

给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:

任何左括号 ( 必须有相应的右括号 )。
任何右括号 ) 必须有相应的左括号 ( 。
左括号 ( 必须在对应的右括号之前 )。
* 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
一个空字符串也被视为有效字符串。

示例 1:
输入: “()”
输出: True

示例 2:
输入: “(*)”
输出: True

示例 3:
输入: “(*))”
输出: True

提示:
1.字符串大小将在 [1,100] 范围内。

题解概要:

1.使用两个栈,一个入(,一个入
2.遇到)则优先用左括号匹配,如果不存在左括号再用
号匹配。
3.当遍历完成后,再把当成右括号和(进行匹配,如果没有号出现在(之前,并且最后(栈中不存在元素,说明符合条件。

代码:

  1. func checkValidString(s string) bool {
  2. starStack := []int{}
  3. leftStack := []int{}
  4. for i,v := range s{
  5. if v == '*'{
  6. starStack = append(starStack,i)
  7. }else if v == '('{
  8. leftStack = append(leftStack,i)
  9. }else{
  10. if len(starStack) == 0 && len(leftStack) == 0{
  11. return false
  12. }
  13. if len(leftStack) > 0{
  14. leftStack = leftStack[:len(leftStack)-1]
  15. continue
  16. }
  17. starStack = starStack[:len(starStack)-1]
  18. }
  19. }
  20. i := len(leftStack)-1
  21. j := len(starStack)-1
  22. for i >= 0 && j >= 0{
  23. if starStack[j] < leftStack[i]{
  24. return false
  25. }
  26. i --
  27. j --
  28. }
  29. return i < 0
  30. }

5. 栈-计算器题目必须 掌握


150. 逆波兰表达式求值 中等

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1:
输入:tokens = [“2”,”1”,”+”,”3”,”“]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1)
3) = 9

题解概要:

标准的栈操作,可参考动画。
点击查看【bilibili】

代码:

  1. import "strconv"
  2. func evalRPN(tokens []string) int {
  3. stack := []int{}
  4. for _,v := range tokens{
  5. if v == "+" {
  6. num1 := stack[len(stack)-1]
  7. num2 := stack[len(stack)-2]
  8. stack[len(stack)-2] = num2 + num1
  9. stack = stack[:len(stack)-1]
  10. }else if v == "-" {
  11. num1 := stack[len(stack)-1]
  12. num2 := stack[len(stack)-2]
  13. stack[len(stack)-2] = num2 - num1
  14. stack = stack[:len(stack)-1]
  15. }else if v == "*" {
  16. num1 := stack[len(stack)-1]
  17. num2 := stack[len(stack)-2]
  18. stack[len(stack)-2] = num2 * num1
  19. stack = stack[:len(stack)-1]
  20. }else if v == "/" {
  21. num1 := stack[len(stack)-1]
  22. num2 := stack[len(stack)-2]
  23. stack[len(stack)-2] = num2 / num1
  24. stack = stack[:len(stack)-1]
  25. }else{
  26. tmp, _ := strconv.Atoi(v)
  27. stack = append(stack,tmp)
  28. }
  29. }
  30. return stack[0]
  31. }

227. 基本计算器 II 中等

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。

示例 1:
输入:s = “3+2*2”
输出:7

示例 2:
输入:s = “ 3/2 “
输出:1

示例 3
输入:s = “ 3+5 / 2 “
输出:5

提示:
1 <= s.length <= 3 105
s 由整数和算符 (‘+’, ‘-‘, ‘
‘, ‘/‘) 组成,中间由一些空格隔开
s 表示一个 有效表达式
表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1] 内
题目数据保证答案是一个 32-bit 整数

题解概要:

  1. 定义 num 储存数字,定义oper 存储上一步操作符。

    1. 遇到 + ,将num入栈;遇到 - ,将 -num 入栈。
      3. 遇到 * ,将栈顶元素出栈与num相乘,最后将乘积入栈。
      4. 遇到 / , 将栈顶元素出栈与num相除,最后将结果入栈。
      5. 最后,将栈中结果相加,即为所求结果。

注意:
本题需要注意边界条件,由于都是遇到下一操作符,才会计算上一表达式的值,所以当遍历到最后一个元素时,要对上一表达式进行计算。

例如:在”3 22” 这一表达式中,当遍历到第二个时,才会对32这一表达式进行计算。

代码:

  1. func calculate(s string) int {
  2. stack := []int{}
  3. oper := '+'
  4. num := 0
  5. for i,ch := range s{
  6. isdight := '0' <= ch && ch <= '9'
  7. if isdight{
  8. num = num*10 + int(ch-'0') //数字有可能是多位数
  9. }
  10. if !isdight && ch != ' ' || i == len(s)-1{ //注意len(s)-1边界条件。
  11. switch oper{
  12. case '+':
  13. stack = append(stack,num)
  14. case '-':
  15. stack = append(stack,-num)
  16. case '*':
  17. //获取栈顶元素
  18. top := stack[len(stack)-1]
  19. stack[len(stack)-1] = top * num
  20. case '/':
  21. //获取栈顶元素
  22. top := stack[len(stack)-1]
  23. stack[len(stack)-1] = top / num
  24. }
  25. num = 0
  26. oper = ch
  27. }
  28. }
  29. ans := 0
  30. for _,v := range stack{
  31. ans += v
  32. }
  33. return ans
  34. }

224. 基本计算器 困难

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

示例 1:
输入:s = “1 + 1”
输出:2

示例 2:
输入:s = “ 2-1 + 2 “
输出:3

示例 3:
输入:s = “(1+(4+5+2)-3)+(6+8)”
输出:23

提示:
1 <= s.length <= 3 * 105
s 由数字、’+’、’-‘、’(‘、’)’、和 ‘ ‘ 组成
s 表示一个有效的表达式

题解概要:

res 表示左边表达式除去栈内保存元素的计算结果;
sign 表示运算符;
num 表示当前遇到的数字,会更新到 res 中;
用栈保存遇到左括号时前面计算好了的结果和运算符。
操作的步骤是:
如果当前是数字,那么更新计算当前数字;
如果当前是操作符+或者-,那么需要更新计算当前计算的结果 res,并把当前数字 num 设为 0,sign 设为正负,重新开始;
如果当前是 ( ,那么说明遇到了右边的表达式,而后面的小括号里的内容需要优先计算,所以要把 res,sign 进栈,更新 res 和 sign 为新的开始;
如果当前是 ) ,那么说明右边的表达式结束,即当前括号里的内容已经计算完毕,所以要把之前的结果出栈,然后计算整个式子的结果;
最后,当所有数字结束的时候,需要把最后的一个 num 也更新到 res 中。

大佬题解:
https://leetcode-cn.com/problems/basic-calculator/solution/ru-he-xiang-dao-yong-zhan-si-lu-lai-zi-y-gpca/

代码:

  1. func calculate(s string) int {
  2. stack := [][]int{} //使用二维数组,数组的第一位存数字,第二位存符号(-1 或 1)。
  3. sign := 1 //符号位。
  4. num := 0 //记录数字
  5. ans := 0 //结果
  6. for _,ch := range s{
  7. if '0' <= ch && ch <= '9'{
  8. num = num *10 + int(ch-'0')
  9. }else if ch == '+' || ch =='-'{
  10. ans += sign * num //计算上一表达式
  11. num = 0 //将num设置为0
  12. //记录操作符
  13. if ch == '-'{
  14. sign = -1
  15. }else{
  16. sign = 1
  17. }
  18. }else if ch == '('{
  19. //遇到 (,则先将 sign与res 推入栈,等)时再推出来。
  20. stack = append(stack,[]int{ans,sign})
  21. //将sign设置为1
  22. sign = 1 //将操作符还原为0
  23. ans = 0 //因为ans暂放到栈中了,因此要将ans存为0
  24. }else if ch == ')'{
  25. //遇到)时,首先计算)内res的值
  26. ans += sign * num
  27. num = 0
  28. sign = 1
  29. //再将之前的值与括号内的值进行计算
  30. top := stack[len(stack)-1]
  31. ans *= top[1]
  32. ans += top[0]
  33. stack = stack[:len(stack)-1]
  34. }
  35. }
  36. ans += sign * num //因为是遇到 操作符时才会计算前一表达式的值, 例如"2+1" 这个等式,当遍历到最后一位1时,就结束了,因此要在这里再计算一次。
  37. return ans
  38. }

1006. 笨阶乘 中等

通常,正整数 n 的阶乘是所有小于或等于 n 的正整数的乘积。例如,factorial(10) = 10 9 8 7 6 5 4 3 2 * 1。

相反,我们设计了一个笨阶乘 clumsy:在整数的递减序列中,我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(*),除法(/),加法(+)和减法(-)。

例如,clumsy(10) = 10 9 / 8 + 7 - 6 5 / 4 + 3 - 2 * 1。然而,这些运算仍然使用通常的算术运算顺序:我们在任何加、减步骤之前执行所有的乘法和除法步骤,并且按从左到右处理乘法和除法步骤。

另外,我们使用的除法是地板除法(floor division),所以 10 * 9 / 8 等于 11。这保证结果是一个整数。

实现上面定义的笨函数:给定一个整数 N,它返回 N 的笨阶乘。

示例 1:
输入:4
输出:7
解释:7 = 4 * 3 / 2 + 1

示例 2:
输入:10
输出:12
解释:12 = 10 9 / 8 + 7 - 6 5 / 4 + 3 - 2 1

*提示:

1 <= N <= 10000
-2^31 <= answer <= 2^31 - 1 (答案保证符合 32 位整数。)

题解概要:

1.本题和227.基本计算器 相似,加减乘除的计算有固定的套路。
2.遇到 *,/ 将计算后的结果赋值给栈顶,遇到+,- 将计算后的结果入栈。
3.最后将栈中数据相加,就是所求结果。

图解:
队列、栈、堆 - 图11
代码:

  1. func clumsy(n int) int {
  2. stack := []int{n}
  3. n --
  4. index := 0
  5. for n > 0 {
  6. switch index%4{
  7. case 0:
  8. top := len(stack)-1
  9. stack[top] *= n
  10. case 1:
  11. top := len(stack)-1
  12. stack[top] /= n
  13. case 2:
  14. stack = append(stack,n)
  15. case 3:
  16. stack = append(stack,-n)
  17. }
  18. index ++
  19. n--
  20. }
  21. ans := 0
  22. for _,v := range stack{
  23. ans += v
  24. }
  25. return ans
  26. }

6. 栈-直方图题目必须 掌握


739. 每日温度 中等

请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]

提示:
1 <= temperatures.length <= 105
30 <= temperatures[i] <= 100

题解概要:

1.遍历整个数组,如果栈不空,且当前数字大于栈顶元素,那么如果直接入栈的话就不是 递减栈 ,所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。

2.继续看新的栈顶元素,直到当前数字小于等于栈顶元素停止,然后将数字入栈,这样就可以一直保持递减栈,且每个数字和第一个大于它的数的距离也可以算出来。

代码:

  1. func dailyTemperatures(temperatures []int) []int {
  2. stack := []int{} //单调递减队列
  3. res := make([]int,len(temperatures))
  4. for i,_ := range temperatures{
  5. //维护单调递减队列
  6. for len(stack) != 0 && temperatures[i] > temperatures[stack[len(stack)-1]]{
  7. index := stack[len(stack)-1]
  8. res[index] = i - index
  9. stack = stack[:len(stack)-1]
  10. }
  11. stack = append(stack,i)
  12. }
  13. return res
  14. }

42. 接雨水 困难

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

队列、栈、堆 - 图12

示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

提示:

  • n == height.length
  • 0 <= n <= 3 * 104
  • 0 <= height[i] <= 105

题解概要:

1.根据提议,我们知道当遇到”凹地”时可接到雨水。因此,需要一个单调递减栈,当遇到比栈顶要大的元素时,栈顶元素为凹地所接的雨水量就可以算出来了。

例如:1,0,2所表示的凹地雨水量是1,如何计算呢?
0的索引是2,1的索引是1,2的索引是3。
凹地的宽度 = 3-1-1 = 1 (凹地右侧索引-凹地左侧索引-1
凹地的高度 = (1-0) = 1 (凹地左右两次中最小的那一侧 - 凹地最底部的高度)

点击查看【bilibili】
代码:

  1. func trap(height []int) int {
  2. stack := []int{} //存索引
  3. ans := 0 //存雨水量
  4. for i,v := range height{
  5. for len(stack) != 0 && v > height[stack[len(stack)-1]]{
  6. top := stack[len(stack)-1] //获取栈顶元素(凹的最低值)
  7. stack = stack[:len(stack)-1] //模拟栈行为,将栈顶元素弹出
  8. for len(stack) > 0 && top == stack[len(stack)-1]{
  9. stack = stack[:len(stack)-1] //模拟栈行为,将栈顶元素弹出
  10. }
  11. if len(stack) > 0 {
  12. left := stack[len(stack)-1]
  13. right := i
  14. height := min(height[left]-height[top],height[right]-height[top])
  15. ans += height * (right-left-1)
  16. }
  17. }
  18. stack = append(stack,i)
  19. }
  20. return ans
  21. }
  22. func min(a int,b int)int{
  23. if a > b {
  24. return b
  25. }
  26. return a
  27. }

84. 柱状图中最大的矩形 困难

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

示例 1:
队列、栈、堆 - 图13
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

提示:

  • 1 <= heights.length <=105
  • 0 <= heights[i] <= 104

题解概要:

1.维护一个单调递增的栈。
2.为了方便计算需要在头部与尾部增加两个元素。
3.可以参考下方题解,讲的非常好。

大佬题解:
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/dong-hua-yan-shi-dan-diao-zhan-84zhu-zhu-03w3/

代码:

  1. func largestRectangleArea(heights []int) int {
  2. stack := []int{-1} //存入的应该是索引
  3. ans := 0
  4. heights = append(heights,0) //尾部加0方便计算。
  5. for i,v := range heights{
  6. //小于时,出栈,计算高度
  7. for len(stack) >0 && stack[len(stack)-1] != -1 && v < heights[stack[len(stack)-1]]{
  8. left := stack[len(stack)-2]
  9. right := i
  10. height := heights[stack[len(stack)-1]]
  11. area := (right-left-1) * height
  12. ans = max(ans,area)
  13. stack = stack[:len(stack)-1]
  14. }
  15. stack = append(stack,i)
  16. }
  17. return ans
  18. }
  19. func max (a int,b int)int{
  20. if a>b{
  21. return a
  22. }
  23. return b
  24. }

7. 栈-其他题目必须 掌握


1047. 删除字符串中的所有相邻重复项 简单

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:
输入:”abbaca”
输出:”ca”
解释:
例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。

提示:
1 <= S.length <= 20000
S 仅由小写英文字母组成。

题解概要:

1.标准的栈操作。遍历字符串,若当前字符与栈顶相同,则栈顶元素出栈。若不相同,入栈。
2.最后,栈中剩余元素即为所求值。

代码:

  1. func removeDuplicates(s string) string {
  2. stack := []rune{}
  3. for _,ch := range s{
  4. if len(stack)!=0 && stack[len(stack)-1] == ch{
  5. stack = stack[:len(stack)-1]
  6. continue
  7. }
  8. stack = append(stack,ch)
  9. }
  10. return string(stack)
  11. }

456. 132 模式 中等

给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。

如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。

示例 1:
输入:nums = [1,2,3,4]
输出:false
解释:序列中不存在 132 模式的子序列。

示例 2:
输入:nums = [3,1,4,2]
输出:true
解释:序列中有 1 个 132 模式的子序列: [1, 4, 2] 。

示例 3:
输入:nums = [-1,3,2,0]
输出:true
解释:序列中有 3 个 132 模式的的子序列:[-1, 3, 2]、[-1, 3, 0] 和 [-1, 2, 0] 。

提示:
n == nums.length
1 <= n <= 2 * 105
-109 <= nums[i] <= 109

题解概要:

  1. 我们要找到nums[i]、nums[j] 和 nums[k]的临界点。
  2. nums[j]最大值,nums[k]是nums[j]右侧的最大值,nums[i] 是 nums[j]左侧的最小值。
  3. 此题可以从右往左进行遍历,维护一个单调递减队列。

队列、栈、堆 - 图14
代码:

  1. import "math"
  2. func find132pattern(nums []int) bool {
  3. stack := []int{}
  4. min := math.MinInt32 //记录得是nums[k]的最大值
  5. for i := len(nums)-1 ; i > -1 ;i--{
  6. //判断是否满足条件
  7. if nums[i] < min{
  8. return true
  9. }
  10. //更新nums[k]的最大值
  11. for len(stack) != 0 && nums[i] > stack[len(stack)-1]{
  12. top := stack[len(stack)-1]
  13. stack = stack[:len(stack)-1]
  14. min = max(min,top)
  15. }
  16. stack = append(stack,nums[i])
  17. }
  18. return false
  19. }
  20. func max(a int,b int) int{
  21. if a > b {
  22. return a
  23. }
  24. return b
  25. }

503. 下一个更大元素 II 中等

给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

示例 1:
输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

示例 2:
输入: nums = [1,2,3,4,3]
输出: [2,3,4,-1,4]

提示:
1 <= nums.length <= 104
-109 <= nums[i] <= 109

题解概要:

1.制造一个循环数组。使用单调栈进行解题。
2.此题需设置ans中各个元素初始化值为-1。
队列、栈、堆 - 图15
代码:

  1. func nextGreaterElements(nums []int) []int {
  2. n := len(nums)
  3. ans := make([]int,n)
  4. //初始化值为-1
  5. for i,_ := range ans{
  6. ans[i] = -1
  7. }
  8. stack := []int{} //存放索引
  9. for i:= 0 ;i < n*2 ;i++{
  10. for len(stack) != 0 && nums[i%n] > nums[stack[len(stack)-1]]{
  11. ans[stack[len(stack)-1]] = nums[i%n]
  12. stack = stack[:len(stack)-1]
  13. }
  14. stack = append(stack,i%n)
  15. }
  16. return ans
  17. }

316. 去除重复字母 中等

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

示例 1:
输入:s = “bcabc”
输出:”abc”

示例 2:
输入:s = “cbacdcbc”
输出:”acdb”

提示:
1 <= s.length <= 104
s 由小写英文字母组成

官方题解:
https://leetcode-cn.com/problems/remove-duplicate-letters/solution/qu-chu-zhong-fu-zi-mu-by-leetcode-soluti-vuso/

代码:

  1. func smallestSubsequence(s string) string {
  2. counts := [26]int{} //记录字符出现次数
  3. stack := []rune{} //记录字符栈
  4. exsit := [26]bool{} //记录字符是否在栈中出现
  5. for _,ch := range s{
  6. counts[ch-'a'] ++
  7. }
  8. for _,ch := range s{
  9. if exsit[ch-'a'] == true{
  10. counts[ch-'a'] --
  11. continue
  12. }
  13. for len(stack) > 0 && ch < stack[len(stack)-1] && counts[stack[len(stack)-1]-'a'] > 0 {
  14. exsit[stack[len(stack)-1]-'a'] = false
  15. stack = stack[:len(stack)-1]
  16. }
  17. stack = append(stack,ch)
  18. counts[ch-'a'] --
  19. exsit[ch-'a'] = true
  20. }
  21. return string(stack)
  22. }

946. 验证栈序列 中等

给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。

示例 1
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。

提示:
1 <= pushed.length <= 1000
0 <= pushed[i] <= 1000
pushed 的所有元素 互不相同
popped.length == pushed.length
popped 是 pushed 的一个排列

题解概要:

1.模拟栈的一个操作即可。
点击查看【bilibili】
代码:

  1. func validateStackSequences(pushed []int, popped []int) bool {
  2. stack := []int{}
  3. for _,v := range pushed{
  4. stack = append(stack,v)
  5. for len(stack) > 0 && popped[0] == stack[len(stack)-1]{
  6. stack = stack[:len(stack)-1]
  7. popped = popped[1:]
  8. }
  9. }
  10. return len(popped) == 0
  11. }

1209. 删除字符串中的所有相邻重复项 II 中等

给你一个字符串 s,「k 倍重复项删除操作」将会从 s 中选择 k 个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。

你需要对 s 重复进行无限次这样的删除操作,直到无法继续为止。

在执行完所有删除操作后,返回最终得到的字符串。

本题答案保证唯一。

示例 1:
输入:s = “abcd”, k = 2
输出:”abcd”
解释:没有要删除的内容。

示例 2:
输入:s = “deeedbbcccbdaa”, k = 3
输出:”aa”
解释:
先删除 “eee” 和 “ccc”,得到 “ddbbbdaa”
再删除 “bbb”,得到 “dddaa”
最后删除 “ddd”,得到 “aa”

题解概要:

使用两个栈,一个栈记录元素,一个栈记录元素连续出现的次数。
如果当前字符与前一个相同,栈顶元素加 1。
否则,往栈中压入 1。
如果栈顶元素等于 k,则从字符串中删除这 k 个字符,并将 k 从栈顶移除。
队列、栈、堆 - 图16
代码:

  1. func removeDuplicates(s string, k int) string {
  2. counts := []int{} //记录栈顶字符的个数
  3. stack := []rune{} //记录栈顶字符
  4. ans := []rune{} //存放结果字符
  5. for _,ch := range s{
  6. if len(stack) == 0 || ch != stack[len(stack)-1]{
  7. stack = append(stack,ch)
  8. counts = append(counts,1)
  9. continue
  10. }
  11. count := counts[len(counts)-1]
  12. if count + 1 != k {
  13. counts[len(counts)-1] += 1
  14. }else{
  15. stack = stack[:len(stack)-1]
  16. counts = counts[:len(counts)-1]
  17. }
  18. }
  19. //处理结果
  20. for i,_ := range stack{
  21. count := counts[i]
  22. str := stack[i]
  23. for j := 0;j < count;j++{
  24. ans = append(ans,str)
  25. }
  26. }
  27. return string(ans)
  28. }

8. 栈-总结


栈的题目一般可以分为4大类:括号问题、计算器问题、直方图问题与其他。

括号问题

例如:1190.反转每队括号的子串、有效的括号、字符串解码等。

对于括号问题,一般会用一到多个变量暂存值。
遇到“(”,变量入栈,同时变量归零。
遇到”)”时,之前暂存的变量出栈,根据题意进行一定的处理。

计算器问题

例如:基本计算器、逆波兰表达式求值、基本计算器2等。

对于计算器问题,要注意字符中数字有可能是多位数。
遇到操作符(+,-,*,/)就计算上一表达式的结果,并将操作符暂存起来。
另外,要注意边界问题。例如”2+1” 这个等式,当遍历到最后一位1时,就结束了,因此要在遍历结束后再进行一次表达式的计算。

直方图问题

例如:42.接雨水、每日温度、柱状图中的最大矩形。

对于直方图问题,重要的是画出图解,进而选择单调递增或递减栈。
并辅以合适的边界条件,简化代码。

其他问题

一般会用到栈的基本操作与单调栈。
要视题目而定,只要是需要暂存的数据,都是可以选择使用栈的。


9. 堆经典题目必须 掌握


347. 前 K 个高频元素 中等

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

题解概要:

1.先用哈希表统计元素出现的频率。
2.再将哈希表的元素放入大根堆中,依次取堆顶元素即可。

代码:

  1. type Heap struct{
  2. heap [][]int
  3. }
  4. func Init()*Heap{
  5. h := &Heap{}
  6. return h
  7. }
  8. // func (h *Heap)init(){
  9. // n := h.len()
  10. // for i := n/2-1;i >= 0;i--{
  11. // h.down(i)
  12. // }
  13. // }
  14. func(h *Heap)len()int{
  15. return len(h.heap)
  16. }
  17. func(h *Heap)swap(i,j int){
  18. h.heap[i],h.heap[j] = h.heap[j],h.heap[i]
  19. }
  20. func(h *Heap)more(i,j int)bool{
  21. return h.heap[i][0] > h.heap[j][0]
  22. }
  23. func (h *Heap)up(i int){
  24. for {
  25. p := (i-1) / 2 //父节点
  26. if p == i || h.more(p,i){
  27. break
  28. }
  29. h.swap(p,i)
  30. i = p
  31. }
  32. }
  33. func (h *Heap)Push(arr []int){
  34. h.heap = append(h.heap,arr)
  35. h.up(h.len()-1)
  36. }
  37. func (h *Heap)down(i int){
  38. for {
  39. left := 2*i+1 //左子节点
  40. if left < 0 || left >= h.len(){
  41. break
  42. }
  43. j := left
  44. if right := left + 1;right < h.len() && h.more(right,left){
  45. j = right
  46. }
  47. if h.more(i,j){
  48. break
  49. }
  50. h.swap(i,j)
  51. i = j
  52. }
  53. }
  54. func (h *Heap)Remove(i int)([]int,bool){
  55. if i < 0 || i >= h.len(){
  56. return []int{},false
  57. }
  58. last := h.len()-1
  59. h.swap(last,i)
  60. x := h.heap[last]
  61. h.heap = h.heap[0:last]
  62. if i ==0 || (h.heap[i][0] > h.heap[(i-1)/2][0]){
  63. h.down(i)
  64. }else{
  65. h.up(i)
  66. }
  67. return x,true
  68. }
  69. func (h *Heap)Pop()([]int,bool){
  70. return h.Remove(0)
  71. }
  72. func topKFrequent(nums []int, k int) []int {
  73. count := make(map[int]int)
  74. for _,v := range nums{
  75. count[v] ++
  76. }
  77. h := Init()
  78. for i,v := range count{
  79. h.Push([]int{v,i})
  80. }
  81. ans := []int{}
  82. for k > 0{
  83. arr,_ := h.Pop()
  84. k--
  85. ans = append(ans,arr[1])
  86. }
  87. return ans
  88. }