返回

栈与队列

用栈实现队列

232. 用栈实现队列 - 力扣(LeetCode)

用两个栈实现先入先出队列

在Go中用切片模拟栈,一个输入栈,一个输出栈

  • 入队:存入输入栈
  • 出队:弹出输出栈顶,若输出栈为空,将输入栈所有数据存入输出栈
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
type MyQueue struct {
    stackIn  []int // 输入栈
    stackOut []int // 输出栈
}

func Constructor() MyQueue {
    return MyQueue{
        stackIn:  make([]int, 0),
        stackOut: make([]int, 0),
    }
}

// 入队
func (this *MyQueue) Push(x int) {
    this.stackIn = append(this.stackIn, x) // 往输入栈追加
}

// 出队
func (this *MyQueue) Pop() int {
    if len(this.stackOut) == 0 { // 输出栈为空
        if len(this.stackIn) == 0 { // 输入栈也为空
            return -1
        }
        for i := len(this.stackIn) - 1; i >= 0; i-- { // 从输入栈顶逐个取元素存入输出栈
            this.stackOut = append(this.stackOut, this.stackIn[i]) // 输入栈元素存入输出栈
        }
        this.stackIn = []int{} // 清空输入栈
    }
    res := this.stackOut[len(this.stackOut)-1]           // 取出出栈元素
    this.stackOut = this.stackOut[:len(this.stackOut)-1] // 更新输出栈
    return res
}

// 获取队头值
func (this *MyQueue) Peek() int {
    res := this.Pop() // 出队
    if res == -1 {    // 队空
        return -1
    }
    this.stackOut = append(this.stackOut, res) // 放回输出栈
    return res
}

// 判队空
func (this *MyQueue) Empty() bool {
    return len(this.stackOut) == 0 && len(this.stackIn) == 0
}
  • 时间复杂度: push和empty为O(1),pop和peek为O(n)
  • 空间复杂度: O(n)

用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)

用两个队列实现一个后入先出(LIFO)的栈

其实这道题目就是用一个队列就够了,一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出对头元素就是栈的顺序了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
type MyStack struct {
    queue []int
}

func Constructor() MyStack {
    return MyStack{
        queue: make([]int, 0),
    }
}

// 进栈
func (this *MyStack) Push(x int) {
    this.queue = append(this.queue, x) // 追加到队列尾
}

// 出栈
func (this *MyStack) Pop() int {
    // count:队首到倒数第二个元素的个数
    for count := len(this.queue) - 1; count != 0; count-- { // 除了最后一个元素其余元素重新加到队尾
        val := this.queue[0]                 // 取一个队首元素
        this.queue = this.queue[1:]          // 队首后面的元素前移
        this.queue = append(this.queue, val) // 重新加到队尾
    }
    res := this.queue[0]                       // 取队首元素
    this.queue = this.queue[1:len(this.queue)] // 更新队列
    return res
}

// 获取栈顶值
func (this *MyStack) Top() int {
    res := this.Pop()                    // 栈顶出栈
    this.queue = append(this.queue, res) // 重新加到队尾
    return res
}

// 判栈空
func (this *MyStack) Empty() bool {
    return len(this.queue) == 0
}
  • 时间复杂度: pop为O(n),其他为O(1)
  • 空间复杂度: O(n)

有效的括号

20. 有效的括号 - 力扣(LeetCode)

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

遍历字符串,遇到左括号加入栈,遇到右括号就弹出栈顶元素(左括号),若不匹配,则返回false,遍历结束后,若栈内还剩左括号,返回false

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func isValid(s string) bool {
    stack := make([]rune, 0)
    for _, v := range s {
        if v == '(' || v == '[' || v == '{' { // 遍历到左括号
            stack = append(stack, v) // 入栈
        } else { // 遍历到右括号
            if len(stack) <= 0 { // 没有左括号
                return false
            }
            if v == ')' && stack[len(stack)-1] != '(' { // 左括号不匹配
                return false
            } else if v == ']' && stack[len(stack)-1] != '[' { // 左括号不匹配
                return false
            } else if v == '}' && stack[len(stack)-1] != '{' { // 左括号不匹配
                return false
            }
            stack = stack[:len(stack)-1] // 出栈
        }
    }
    if len(stack) > 0 { // 还剩左括号未匹配
        return false
    }
    return true
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

优化代码:哈希表,返回判断结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func isValid(s string) bool {
    hash := map[rune]rune{')': '(', ']': '[', '}': '{'}
    stack := make([]rune, 0)
    for _, v := range s {
        if v == '(' || v == '[' || v == '{' { // 遍历到左括号
            stack = append(stack, v) // 入栈
        } else if len(stack) > 0 && stack[len(stack)-1] == hash[v] { // 遍历到右括号栈非空且匹配成功
            stack = stack[:len(stack)-1] // 出栈
        } else { // 遍历到右括号栈空或匹配不成功
            return false
        }
    }
    return len(stack) == 0
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

删除字符串中的所有相邻重复项

1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。

遍历字符串,若栈为空或与栈顶元素不同,则入栈,若与栈顶元素不同,则栈顶元素出栈,遍历结束后,将栈中元素拼接为字符串返回

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func removeDuplicates(s string) string {
    stack := make([]rune, 0)
    for _, v := range s {
        if len(stack) == 0 || stack[len(stack)-1] != v { // 栈为空或与栈顶元素不同
            stack = append(stack, v) // 入栈
        } else { // 栈非空且与栈顶元素相同
            stack = stack[:len(stack)-1] // 出栈
        }
    }
    return string(stack)
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

逆波兰表达式求值

150. 逆波兰表达式求值 - 力扣(LeetCode)

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

遍历给定字符串,遇到运算符就将栈顶两个元素取出用该运算符处理,结果再入栈,遍历结束返回栈中的运算结果

注意:除法中栈顶元素是除数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func evalRPN(tokens []string) int {
    stack := make([]int, 0)
    for _, v := range tokens {
        if v == "+" { // 遍历到运算符
            val := stack[len(stack)-2] + stack[len(stack)-1] // 获取运算结果
            stack = stack[:len(stack)-1]                     // 更新栈
            stack[len(stack)-1] = val                        // 结果入栈
        } else if v == "-" { // 遍历到运算符
            val := stack[len(stack)-2] - stack[len(stack)-1] // 获取运算结果
            stack = stack[:len(stack)-1]                     // 更新栈
            stack[len(stack)-1] = val                        // 结果入栈
        } else if v == "*" { // 遍历到运算符
            val := stack[len(stack)-2] * stack[len(stack)-1] // 获取运算结果
            stack = stack[:len(stack)-1]                     // 更新栈
            stack[len(stack)-1] = val                        // 结果入栈
        } else if v == "/" { // 遍历到运算符
            val := stack[len(stack)-2] / stack[len(stack)-1] // 获取运算结果
            stack = stack[:len(stack)-1]                     // 更新栈
            stack[len(stack)-1] = val                        // 结果入栈
        } else { // 遇到数字
            val, _ := strconv.Atoi(v)
            stack = append(stack, val) // 入栈
        }
    }
    return stack[0]
}
  • 时间复杂度:O(n)
  • 空间复杂发:O(n)

压缩代码:swich

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func evalRPN(tokens []string) int {
    stack := make([]int, 0)
    for _, v := range tokens {
        val, err := strconv.Atoi(v) // 转为数字
        if err == nil {             // 说明是数字
            stack = append(stack, val) // 数字入栈
        } else { // 说明是运算符
            switch v {
                case "+": stack[len(stack)-2] = stack[len(stack)-2] + stack[len(stack)-1] // 运算结果入栈
                case "-": stack[len(stack)-2] = stack[len(stack)-2] - stack[len(stack)-1] // 运算结果入栈
                case "*": stack[len(stack)-2] = stack[len(stack)-2] * stack[len(stack)-1] // 运算结果入栈
                case "/": stack[len(stack)-2] = stack[len(stack)-2] / stack[len(stack)-1] // 运算结果入栈
            }
            stack = stack[:len(stack)-1] // 更新栈
        }
    }
    return stack[0]
}
  • 时间复杂度:O(n)
  • 空间复杂发:O(n)

滑动窗口最大值

239. 滑动窗口最大值 - 力扣(LeetCode)

给定一个数组和整数k,用k作为滑动窗口大小,窗口从最左侧移动到最右侧,每次移动一位,返回每次窗口移动后的最大值

单调队列:队首出队,队尾入队,队内元素单调;

  1. pop(value):若窗口移除的元素等于单调队列的队首元素,则队列弹出元素,否则不用任何操作
  2. push(value):若push的元素大于队尾元素,则弹出队尾元素,直到push的元素小于等于队尾元素或队空

保持如上规则,每次窗口移动的时候,只要取队首即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
type MyQueue struct {
    queue []int
}

func Constructor() *MyQueue {
    return &MyQueue{
        queue: make([]int, 0),
    }
}

// 获取队首元素
func (m *MyQueue) Front() int {
    return m.queue[0]
}

// 获取队尾元素
func (m *MyQueue) Back() int {
    return m.queue[len(m.queue)-1]
}

// 判队空
func (m *MyQueue) Empty() bool {
    return len(m.queue) == 0
}

// 入队
func (m *MyQueue) Push(val int) {
    for !m.Empty() && val > m.Back() { // 队非空且入队元素大于队尾元素
        m.queue = m.queue[:len(m.queue)-1] // 弹出队尾元素
    }
    m.queue = append(m.queue, val) // 入队尾
}

// 出队
func (m *MyQueue) Pop(val int) {
    if !m.Empty() && val == m.Front() { // 队非空且出队元素等于队首元素
        m.queue = m.queue[1:] // 队首出队
    }
}

func maxSlidingWindow(nums []int, k int) []int {
    queue := Constructor()
    res := make([]int, 0)
    for i := 0; i < k; i++ { // 前k个元素入队
        queue.Push(nums[i]) // 入队
    }
    res = append(res, queue.Front()) // 记录前k个元素的最大值
    for i := k; i < len(nums); i++ { // 遍历数组   
        queue.Pop(nums[i-k]) // 滑动窗口移除最前面的元素
        queue.Push(nums[i]) // 滑动窗口添加最后面的元素
        res = append(res, queue.Front()) // 记录最大值
    }
    return res
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(k)

前 K 个高频元素

347. 前 K 个高频元素 - 力扣(LeetCode)

给定一个数组和一个整数 k ,返回数组中出现频率前 k 高的元素

先统计频率,再对频率排序;统计元素出现的频率可以用map;对频率进行排序,可以用快排和堆

快排

用map统计完频率后,收集不重复的元素,利用字典值对不重复的元素排序,最后返回前k个不重复的元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func topKFrequent(nums []int, k int) []int {
    res := make([]int, 0)
    mp := make(map[int]int)
    for _, v := range nums { // 遍历数组
        mp[v]++ // 字典值(频率)加一
    }
    for key := range mp { // 遍历字典key
        res = append(res, key) // 记录不重复的元素
    }
    sort.Slice(res, func(a, b int) bool {
        return mp[res[a]] > mp[res[b]] // 降序
    })
    return res[:k]
}
  • 时间复杂度: O(nlogn)
  • 空间复杂度: O(n)

用map统计完频率后,构建小顶堆

是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果是父结点大于等于左右孩子那就是大顶堆,小于等于左右孩子就是小顶堆。大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),用小顶堆每次将最小的元素弹出,最后小顶堆里积累的就是前k个最大元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
type Heap [][2]int // 二维表示元素及其频率

// 交换两个节点
func (h Heap) swap(i, j int) {
    h[i], h[j] = h[j], h[i]
}

// 比较两个节点
func (h Heap) less(i, j int) bool {
    return h[i][1] < h[j][1] // 比较频率 如果实现的是大顶堆,修改这里的判断即可
}

// 节点向上比较交换
func (h Heap) up(i int) {
    for {
        f := (i - 1) / 2            // 父节点在数组中的位置
        if i == f || h.less(f, i) { // 当前节点为根节点或父节点小于当前节点
            break
        }
        h.swap(f, i) // 交换当前节点和父节点的值
        i = f        // 更新当前节点索引继续向上比较
    }
}

// 注意go中所有参数都是值传递 所以要让h的变化在函数外也起作用 此处要传指针
// 添加节点
func (h *Heap) Push(x [2]int) {
    *h = append(*h, x) // 加入末尾
    h.up(len(*h) - 1)  // 向上比较交换
}

// 节点向下比较交换
func (h Heap) down(i int) {
    for {
        l := 2*i + 1     // 左孩子
        r := 2*i + 2     // 右孩子
        if l >= len(h) { // 当前节点已经是叶子节点
            break
        }
        minChild := l                   // 默认左孩子值小于右孩子值
        if r < len(h) && h.less(r, l) { // 右孩子存在且小于左孩子
            minChild = r // 更新当前节点的最小子节点
        }
        if h.less(i, minChild) { // 当前节点小于最小子节点
            break
        }
        h.swap(i, minChild) // 交换当前节点和最小子节点位置
        i = minChild        // 更新当前节点索引继续向下比较
    }
}

// 删除堆中位置为i的节点 返回被删节点的值
func (h *Heap) Remove(i int) ([2]int, bool) {
    if i < 0 || i > len(*h)-1 { // 索引越界
        return [2]int{0, 0}, false
    }
    n := len(*h) - 1 // 最后一个节点的索引
    h.swap(i, n)     // 交换要被删除节点和最后的节点
    x := (*h)[n]     // 保存最后节点值
    *h = (*h)[0:n]   // 删除最后节点
    h.down(i)        // 被交换的当前节点向下比较交换
    return x, true
}

// 弹出堆顶的元素,并返回其值
func (h *Heap) Pop() [2]int {
    x, _ := h.Remove(0)
    return x
}

// 根据传入的切片建堆
func BuildHeap(arr [][2]int) Heap {
    h := Heap(arr)                       // 获取给定切片
    for i := len(h)/2 - 1; i >= 0; i-- { // 从堆中末尾节点的父节点到根节点
        h.down(i) // 向下比较交换
    }
    return h
}

func topKFrequent(nums []int, k int) []int {
    mp := map[int]int{}
    res := make([]int, 0)
    for _, v := range nums { // 遍历数组
        mp[v]++ // 字典值(频率)加一
    }
    h := BuildHeap([][2]int{})   // 初始化堆
    for key, value := range mp { // 遍历字典
        h.Push([2]int{key, value}) // 元素入堆
        if len(h) > k {            // 堆的节点数超过k个
            h.Pop() // 删除顶点(min)
        }
    }
    for i := 0; i < k; i++ { // 遍历堆
        res = append(res, h.Pop()[0])
    }
    return res
}
  • 时间复杂度: O(nlogk)
  • 空间复杂度: O(n)

使用go中标准库提供的heap,需要先实现两个接口定义的五个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
type Heap [][2]int // 二维表示元素及其频率

func (h Heap) Len() int {
    return len(h)
}

// 比较两个节点
func (h Heap) Less(i, j int) bool {
    return h[i][1] < h[j][1] // 按照频率排序节点
}

// 交换两个节点
func (h Heap) Swap(i, j int) {
    h[i], h[j] = h[j], h[i]
}

// go中所有参数都是值传递 所以要让h的变化在函数外也起作用 此处要传指针
// 添加节点
func (h *Heap) Push(x interface{}) {
    *h = append(*h, x.([2]int)) // 向末尾添加节点
}

// 删除节点
func (h *Heap) Pop() interface{} {
    old := *h         // 保存当前根
    n := len(old)     // 节点数
    x := old[n-1]     // 保存最后一个节点
    *h = old[0 : n-1] // 删除最后一个节点
    return x          // 返回删除的节点
}

func topKFrequent(nums []int, k int) []int {
    mp := map[int]int{}
    res := make([]int, k)
    for _, v := range nums { // 遍历数组
        mp[v]++ // 字典值(频率)加一
    }
    h := &Heap{}                 // 初始化堆
    heap.Init(h)                 // 用go标准库提供的heap
    for key, value := range mp { // 遍历字典
        heap.Push(h, [2]int{key, value}) // 元素入堆
        if h.Len() > k {                 // 堆的节点数超过k个
            heap.Pop(h) // 删除顶点(min)
        }
    }
    for i := 0; i < k; i++ { // 遍历堆
        res[k-i-1] = heap.Pop(h).([2]int)[0] // 将堆中排序后的元素加入结果集
    }
    return res
}
Built with Hugo
Theme Stack designed by Jimmy