算法练习-第十三天【栈与队列】
栈与队列
150. 逆波兰表达式求值
思路
根据提示,逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。那么可以使用栈来处理逆波兰表达式的计算。
当当前的字符是符号时,依次取出栈顶的2个元素。并根据符号进行运算,将计算后的结果插入回栈顶,继续向下运算。
func evalRPN(tokens []string) int {
stack := []int{}
for _, token := range tokens {
num, err := strconv.Atoi(token)
if err != nil {
num1, num2 := stack[len(stack)-2], stack[len(stack)-1]
stack = stack[:len(stack)-2]
switch token {
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, num)
}
}
return stack[0]
}
总结
后缀表达式对于计算机来讲是友好的,因为利用栈的顺序来进行处理,不需要考虑回退等问题。
239. 滑动窗口最大值
思路
根据题意求滑动窗口的最大值,第一想法是通过遍历数组,然后每次计算滑动窗口k范围内的最大值。时间复杂度是,很遗憾这种解法超时了。
func maxSlidingWindow(nums []int, k int) []int {
rlt := []int{}
rlt = append(rlt, maxValue(nums[:k]))
for i := k + 1; i <= len(nums); i++ {
rlt = append(rlt, maxValue(nums[i-k:i]))
}
return rlt
}
func maxValue(subnums []int) int {
maxVal := math.MinInt32
for _, num := range subnums {
maxVal = max(maxVal, num)
}
return maxVal
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
进阶
因为滑动窗口是一个元素进一个元素出这种方式,那么可以考虑使用大顶堆或者队列来完成。
- 首先如果使用大顶堆时,堆顶是最大值,当窗口滑动时,每次只能移除最大值,无法移除其他的数值,那么就排除掉大顶堆来实现的可能。
- 滑动窗口一个元素进一个元素出的模式很适合队列,要想获得最大值,就需要将最大值放到队列的出口处,每次调用
queue.Front()
就可以获取,整个队列如果维护滑动窗口内的所有数据并且从大到小排序的话,移除或添加元素会很复杂。其实并不需要关心滑动窗口内的所有元素,只需要关注可能成为最大值的元素即可。 - 维护元素单调递减/递增的队列为单调队列
- 此题采用单调递减队列时
queue.Push(int value)
操作时,如果当前的元素值比队列尾部的大,则需要依次移除尾部小于此元素值的元素。queue.Pop()
操作时,如果当前元素等于队列头部元素则移除,否则不做任何操作。
func maxSlidingWindow(nums []int, k int) []int {
// 单调队列。从大到小
q := []int{}
push := func(value int){
// 新的值是否大于队尾值 则删除队尾值
for len(q) >0 && value > q[len(q)-1]{
q = q[:len(q)-1]
}
q = append(q, value)
}
// 一次只移动一个
pop := func(value int){
if len(q) > 0 && value == q[0]{
q = q[1:]
}
}
// 向队列添加k个元素
for i := 0; i < k; i++{
push(nums[i])
}
ans := []int{}
ans = append(ans, q[0])
for i := k; i < len(nums); i++{
pop(nums[i-k])
push(nums[i])
ans = append(ans, q[0])
}
return ans
}
自定义一个队列的实现方式
type MyQueue struct {
queue []int
}
func (q *MyQueue) Front() int {
return q.queue[0]
}
func (q *MyQueue) Back() int {
return q.queue[len(q.queue)-1]
}
func (q *MyQueue) Push(x int) {
for len(q.queue) > 0 && x > q.Back() {
q.queue = q.queue[:len(q.queue)-1]
}
q.queue = append(q.queue, x)
}
func (q *MyQueue) Pop(x int) {
if len(q.queue) > 0 && x == q.Front() {
q.queue = q.queue[1:]
}
}
// 单调递减队列
func NewMyQueue() *MyQueue {
return &MyQueue{queue: []int{}}
}
func maxSlidingWindow(nums []int, k int) []int {
rlt := []int{}
q := NewMyQueue()
for i := 0; i < k; i++ {
q.Push(nums[i])
}
rlt = append(rlt, q.Front())
for i := k; i < len(nums); i++ {
q.Pop(nums[i-k])
q.Push(nums[i])
rlt = append(rlt, q.Front())
}
return rlt
}
总结
单调队列中的push
和pop
操作可以根据题意来进行处理并不是固定的。
347.前 K 个高频元素
思路
统计前 K 个高频元素,需要进行如下三步:
- 使用map统计每个元素的频次
- 对map按照频次进行排序
- 取前 K 个元素
其中第二步对频次进行排序,因为快排时对整个数组元素进行排序,而本题只需要维护 K 个元素即可,因此使用到了优先队列。
优先队列是依靠堆来实现的,堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。
- 大顶堆:父亲结点大于等于左右孩子
- 小顶堆:父亲节点小于等于左右孩子
统计前 K 个高频元素如果使用大顶堆的话,堆顶是最大值,那么每次更新堆的时候移除的都是最大值,这不符合预期
使用小顶堆只需要维护 K 个频次元素,当有大于堆顶的频次进入时,移除堆顶元素。最后最小堆内的K个元素就是最高频的 K 个元素。
func topKFrequent(nums []int, k int) []int {
m := map[int]int{}
for _, num := range nums {
m[num]++
}
h := &hp{}
heap.Init(h)
for value, cnt := range m {
heap.Push(h, &pair{value, cnt})
if h.Len() > k {
heap.Pop(h)
}
}
rlt := make([]int, k)
for i := 0; i < k; i++ {
rlt[k-i-1] = heap.Pop(h).(*pair).value
}
return rlt
}
type pair struct {
value, cnt int
}
type hp []*pair
func (h hp) Len() int { return len(h) }
func (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h hp) Less(i, j int) bool { return h[i].cnt < h[j].cnt }
func (h *hp) Push(x interface{}) {
*h = append(*h, x.(*pair))
}
func (h *hp) Pop() interface{} {
a := *h
x := a[len(a)-1]
a = a[:len(a)-1]
*h = a
return x
}
总结
使用小顶堆只需要维护 K 个元素,时间复杂度是。如果使用排序算法那么时间复杂度是。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!