代码随想录算法训练营Day10 | 150. 逆波兰表达式求值,239. 滑动窗口最大值,347. 前 K 个高频元素

目录

150. 逆波兰表达式求值

239. 滑动窗口最大值

347. 前 K 个高频元素


150. 逆波兰表达式求值

题目链接:150. 逆波兰表达式求值 - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:无

        理解了逆波兰表达式 (后缀表达式) 怎么计算的就好。遇到数字则入栈,遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中,依次迭代。

        Tips:

        ①后缀表达式对计算机来说非常友好,无需判断中缀表达式的括号、运算符优先级等。

        ②str.isdigit()只能判断正数,由于负数前有个'-',.isdigit()会将负数判为False。因此只要去掉负号再判断就行,即str.lstrip('-').isdigit()。

        ③python3的'//'是地板除法,1.5→1,-1.5→-2;'/'是浮点数除法,-1.5→-1.5。想要计算结果向0截断,可使用int(a/b),1.5→1,-1.5→-1。

        ④使用eval()函数可将字符形式的运算符'+'转为能计算的运算符。另外,eval()计算速度相对较慢,也可以定义一个字符运算符和真正运算符的字典进行映射。

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for i in tokens:
            if i.lstrip('-').isdigit(): # 元素为数字,入栈
                stack.append(i)
            else: # 元素为运算符,将两个元素出栈进行计算,并将计算结果入栈
                b = stack.pop()
                a = stack.pop()
                if i == '/': # 题目要求除法向0截断,故额外定义一下
                    val = str(int(eval(a+i+b)))
                else:
                    val = str(eval(a+i+b))
                stack.append(val)
        res = int(stack.pop())
        return res

    # 时间复杂度 O(n)
    # 空间复杂度 O(n)

239. 滑动窗口最大值

题目链接:239. 滑动窗口最大值 - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:只想到暴力解,没想到优化解法

        本题需要左弹出和右弹出,故使用双端队列deque作为基础数据结构。同时队列中元素保持了从大到小的单调性,因此也为单调队列。

        队列中没有必要维护滑动窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素,同时保证队列里的元素数值是由大到小的。

        队列的弹出:如果滑动窗口移除的元素等于队尾元素,那么弹出队尾元素,否则不用任何操作。每次pop元素,移除的都是队列最左侧的值 (确认最大值还在不在窗口里)。

        队列的加入:如果加入的元素大于队头元素的数值,那么就将队头的元素弹出,直到加入的元素小于队头的元素为止。每次push元素,从右侧开始循环移除比自己小的值 (保证队列是递减的,而且每一个新的值都可以加入队列)。

        本题核心就是,我们维护的是一个动态的实力排行榜 (这里体现的就是单调,实力越强,排名越靠前),那么第一名自然是最强的 (队头是最大的元素)。就像一个擂台,当有新人进来,他会把不如自己的人pk下去,更新排行榜。当老大退役 (pop),第二名就变成了第一名。 

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        from collections import deque
        res = []
        mque = deque() # 单调队列,队列中元素队尾最大,队头最小
        for i in range(len(nums)):
            while mque and mque[-1] < nums[i]: # 若当前元素比队头元素大,弹出队头
                mque.pop()
            mque.append(nums[i]) # 当前元素入队
            if i >= k-1: # 当经过的元素达到了滑动窗口宽度后
                res.append(mque[0]) # 将队尾最大的元素保存到结果中,该元素即为当前窗口的最大元素
                if mque and mque[0] == nums[i-k+1]: # 只有当窗口滑过该元素且该元素位于队尾时
                    mque.popleft() # 弹出队尾的当前窗口最大元素
        return res

    # 时间复杂度 O(n)
    # 空间复杂度 O(k) 辅助队列的长度

347. 前 K 个高频元素

题目链接:347. 前 K 个高频元素 - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:第一次接触堆,很多东西都很模糊

        本题思路:①要统计元素出现频率;②对频率排序;③找出前K个高频元素。①使用字典完成,②③使用优先级队列完成。

        优先级队列,也被称为堆,实际是以list为表现形式的完全二叉树,树中每个结点的值都不小于 (或不大于) 其左右孩子的值。如果父结点大于等于左右子结点就是大顶堆,小于等于左右子结点就是小顶堆。入队时,每个元素带有某种优先级信息,要将其放在合适的位置上;出队时,删除优先级最高或最低的元素。普通队列可以看作以入队时间为优先级的优先级队列 (入队越早,优先级越高,越早出队)。

        使用大顶堆还是小顶堆?小顶堆,因为要统计最大前K个元素,只有小顶堆每次将最小的元素弹出,最后堆里积累的才是前K个最大元素。

        Tips:

        heapq.heappush(heap, item),如果item是一个元组,默认将排序规则应用到第一个元素上。

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        import heapq
        from collections import defaultdict
        
        freq_count = defaultdict(int) # 统计数字出现的频率(次数)
        for i in range(len(nums)):
            freq_count[nums[i]] += 1

        pri_que = [] # 以小顶堆创建优先级队列对频率进行排序,小顶堆大小为K
        for num, freq in freq_count.items(): # 迭代完毕后,堆中只留下K个频率最大的元素
            heapq.heappush(pri_que, (freq, num))
            if len(pri_que) > k: # 如果堆的大小>K
                heapq.heappop(pri_que) # 弹出顶端的低频元素
        
        res = [0] * k
        for i in reversed(range(0, k)): # 小顶堆先弹出最小元素,故倒序输出到结果数组
            res[i] = heapq.heappop(pri_que)[1]
        return res

    # 时间复杂度 O(nlogk) k为堆的大小
    # 空间复杂度 O(n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值