用栈实现队列
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
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
遍历字符串,遇到左括号加入栈,遇到右括号就弹出栈顶元素(左括号),若不匹配,则返回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
}
|
优化代码:哈希表,返回判断结果
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
}
|
删除字符串中的所有相邻重复项
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)
}
|
逆波兰表达式求值
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]
}
|
压缩代码: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]
}
|
滑动窗口最大值
239. 滑动窗口最大值 - 力扣(LeetCode)
给定一个数组和整数k,用k作为滑动窗口大小,窗口从最左侧移动到最右侧,每次移动一位,返回每次窗口移动后的最大值
单调队列:队首出队,队尾入队,队内元素单调;
- pop(value):若窗口移除的元素等于单调队列的队首元素,则队列弹出元素,否则不用任何操作
- 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
}
|
前 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
}
|