代码随想录 Day 11 | 【第五章 栈与队列】150.逆波兰表达式求值、239.滑动窗口最大值、347.前 K 个高频元素、总结

一、150.逆波兰表达式求值

本题不难,但第一次做的话,会很难想到,所以先看视频,了解思路再去做题

题目链接/文章讲解/视频讲解:代码随想录

1. 看完代码随想录的想法

(1)首先需要充分理解什么是逆波兰表达式,相当于树中的后缀表达式,与平时使用的中序表达式并不相同。

        定义一个初始化的空栈,然后去遍历输入的逆波兰表达式,遇到数字就向栈中添加数字元素,遇到运算符就取出栈顶的两个数字进行运算,再存放进栈中。直到最后全部遍历完成,输出最后结果。

(2)首先,从operator引入add, sub, mul运算;然后由于题目中要求两个整数之间的除法总是向零截断,不包含除0运算,所以额外去定义一个除法函数,防止除数为0,处理异常值。

(3)其次,定义一个空栈stack,然后去遍历输入的字符串数组,如果字符不是加减乘除中任意一个,那么说明是数字,则用int强制将char转化为整数,然后添加进栈中;如果遍历到的字符是运算符,则依次取出两个,然后将运算后的数添加进栈中;

(4)全部遍历完成后返回栈顶元素。

from operator import mul, add, sub

def div(x, y):
    if x * y > 0:
        return int(x/y)
    else:
        return -(abs(x) // abs(y))

class Solution(object):
    op_map = {'+': add, '-': sub, '*': mul, '/': div}

    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for i in tokens:
            if i not in {'+', '-', '*', '/'}:
                stack.append(int(i))
            else:
                op2 = stack.pop()
                op1 = stack.pop()
                stack.append(self.op_map[i](op1,op2))
        return stack.pop()

2. 实现过程中遇到的困难及解决

(1)如何判断循环到的元素是数字:如果不在运算符字典中,那么就是数字;或者使用isdigit()函数进行判断。

(2)如何弹出栈顶两个元素进行运算:直接pop()一次弹出,并且将运算后的数再写入栈中,便于下次运算;要注意运算符是调用字典,用key取value运算函数。

(3)如何进行异常处理:对分母为0以及负数进行特别处理,通过取整、取绝对值的方式。

(4)实现过程中debug:1)空格没对齐报错;2)op1、op2的顺序需要特别注意,op1是第二个取出的,op2是栈顶元素,并且要按照先后放入栈的顺序进行运算。

二、239.滑动窗口最大值

之前讲的都是栈的应用,这次该是队列的应用了。

本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。

题目链接/文章讲解/视频讲解:代码随想录

1. 看完代码随想录的想法

(1)整体思路:按照给定k的大小去圈定滑动窗口,每次返回滑动窗口中的最大值,并将其添加进入结果集中;所以自定义一个队列,维护队列出口元素为滑动窗口最大值,只要新添加进来的元素比队列出口的元素大,那么把队列出口的元素全部弹出,然后将新元素添加进入队列入口,并且返回队列出口元素,将其放进结果集中,表示这是这一个滑动窗口的最大值。

(2)首先,自定义一个能完成上述操作的新类,然后最后在给定的主类中调用自定义类方法。

(3)自定义新类:首先引入单调队列,定义一个初始化队列方法;然后定义pop()函数,弹出之前必须判断队列是否为空,不为空才可以弹出,同时需要判断当前要弹出的数值是否是队列出口元素,必须在队列出口才可以弹出元素。

        接下来定义push()函数,同样判断队列是否为空,并且如果需要添加的元素大于入口处的元素,那么就将比需添加元素的数值小的元素全部弹出,直到需要添加的数值小于等于队列入口元素,这样才能保持队列的元素单调从大到小。

        最后定义获取滑动窗口最大值的函数front(),直接返回队列前端即可,因为该队列是由大到小排序的。

(4)主函数:首先定义一个新队列,调用自定义类方法,然后定义一个空数组,用于存放滑动窗口的最大元素。接下来,将给定的前k个元素调用push函数放进队列,最后调用front函数,将这组滑动窗口的最大值存放进入数组。然后继续向后遍历给定数组,从第k个一直遍历到数组末尾,然后调用pop函数移除最前面元素,在队列末尾添加元素,同时记录滑动窗口最大值。最后返回result。

from collections import deque


class MyQueue: #单调队列(从大到小
    def __init__(self):
        self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
    
    #每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
    #同时pop之前判断队列当前是否为空。
    def pop(self, value):
        if self.queue and value == self.queue[0]:
            self.queue.popleft()#list.pop()时间复杂度为O(n),这里需要使用collections.deque()
            
    #如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
    #这样就保持了队列里的数值是单调从大到小的了。
    def push(self, value):
        while self.queue and value > self.queue[-1]:
            self.queue.pop()
        self.queue.append(value)
        
    #查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
    def front(self):
        return self.queue[0]
    
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        que = MyQueue()
        result = []
        for i in range(k): #先将前k的元素放进队列
            que.push(nums[i])
        result.append(que.front()) #result 记录前k的元素的最大值
        for i in range(k, len(nums)):
            que.pop(nums[i - k]) #滑动窗口移除最前面元素
            que.push(nums[i]) #滑动窗口前加入最后面的元素
            result.append(que.front()) #记录对应的最大值
        return result

三、347.前 K 个高频元素

大/小顶堆的应用, 在C++中就是优先级队列

本题是 大数据中取前k值 的经典思路,了解想法之后,不算难。

题目链接/文章讲解/视频讲解:代码随想录

1. 看完代码随想录的想法

(1)整体思路:首先构建map,用于统计数组中每个元素出现的频率,key-value形式;因为需要统计的是高频元素,所以每次弹出的元素不可以是最大的,因此使用小顶堆。

(2)定义一个空字典defaultdict(int),用于统计输入的数组中每个元素及其出现的频率;

        然后定义一个新字典,用于将上述字典的key和value交换,然后将这个字典的出现频率转换为数组形式,并对其进行排序,也就是将频率由小到大进行排序;

        接下来定义一个result空数组用于存放结果集,以及一个计数器cnt,初始化为0;然后对输入的数组进行遍历,通过key[-1]找到字典中频率最高的元素,将其存入结果集,同时将计数器+1,同时删除较小元素,一直到满足前k个高频元素全部存入结果集。

        最后返回结果集即可。

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        value_dict = defaultdict(int)
        for i in nums:
            value_dict[i] += 1
        index_dict = defaultdict(list)
        for key in value_dict:
            index_dict[value_dict[key]].append(key)
        key = list(index_dict.keys())
        key.sort()
        result = []
        cnt = 0
        while key and cnt != k:
            result += index_dict[key[-1]]
            cnt += len(index_dict[key[-1]])
            key.pop()
        return result

 

四、总结

在栈与队列系列中,强调栈与队列的基础,也是很多同学容易忽视的点。

使用抽象程度越高的语言,越容易忽视其底层实现,而C++相对来说是比较接近底层的语言。

用栈实现队列,用队列实现栈来掌握的栈与队列的基本操作。

接着,通过括号匹配问题、字符串去重问题、逆波兰表达式问题来系统讲解了栈在系统中的应用,以及使用技巧。

通过求滑动窗口最大值,以及前K个高频元素介绍了两种队列:单调队列和优先级队列,这是特殊场景解决问题的利器,是一定要掌握的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值