代码随想录算法训练营第十三天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347. 前 K 个高频元素、栈与队列总结

92 阅读5分钟

150. 逆波兰表达式求值

代码随想录视频讲解

代码随想录文章讲解

使用栈

  • post-order转in-order

    • Post-order: left-right-root
    • In-order: left-root-right
  • 记得把result push进stack

class Solution:
    def evalRPN(self, tokens):
    
        stack = []
​
        for token in tokens:
​
            if token not in "+-/*":
                stack.append(int(token))
                continue
​
            number_2 = stack.pop()
            number_1 = stack.pop()
​
            result = 0
            if token == "+":
                result = number_1 + number_2
            elif token == "-":
                result = number_1 - number_2
            elif token == "*":
                result = number_1 * number_2
            else:
                result = int(number_1 / number_2)
​
            stack.append(result)
​
        return stack.pop()

239. 滑动窗口最大值

代码随想录视频讲解

代码随想录文章讲解

使用单调递减队列

  • 使用deque来实现单调递减队列
  • 维护一个到index i为止的,最大长度为k的单调递减队列
  • 当一个元素被移出窗口时,也将它从队列中移除
  • 当一个元素被移入窗口时,将它push进队列,并维护队列的单调性
# Time complexity: O(N)
# Space complexity: O(k)
from collections import deque
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        # decreasing queue
        class MyQueue:
            def __init__(self):
                self.my_queue = deque()
            
            # if max is moving from the window, pop it.
            def pop(self, value):
                if self.my_queue and value == self.my_queue[0]:
                    self.my_queue.popleft()
​
            def push(self, x):
                while self.my_queue and self.my_queue[-1] < x:
                    self.my_queue.pop()
                self.my_queue.append(x)
​
            def front(self):
                return self.my_queue[0]
​
        my_queue = MyQueue()
        for i in range(k):
            my_queue.push(nums[i])
​
        res = [my_queue.front()]
        my_queue.pop(nums[0])
        l, r = 1, k
        while r < len(nums):
            my_queue.push(nums[r])
            res.append(my_queue.front())
            my_queue.pop(nums[l])
            l += 1
            r += 1
​
        return res

347. 前 K 个高频元素

代码随想录视频讲解

代码随想录文章讲解

使用库函数,数个数然后排序

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        nums_count = Counter(nums)
        sort_nums_count = sorted(
            nums_count.items(), key=lambda x: x[1], reverse=True)
        return [sort_nums_count[i][0] for i in range(k)]

使用heapq.nlargest

  • heapq.nlargest(n, iterable, key=None)

    iterable 所定义的数据集中返回前 n 个最大元素组成的列表。 如果提供了 key 则其应指定一个单参数的函数,用于从 iterable 的每个元素中提取比较键 (例如 key=str.lower)。 等价于: sorted(iterable, key=key, reverse=True)[:n]

heapq.nlargestn 值较小时性能最好。 对于更大的值,使用 sorted() 函数会更有效率。 此外,当 n==1 时,使用内置的 min()max() 函数会更有效率。 如果需要重复使用这些函数,请考虑将可迭代对象转为真正的堆。

from collections import Counter
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]: 
        # O(1) time 
        if k == len(nums):
            return nums
        
        # 1. build hash map : character and how often it appears
        # O(N) time
        count = Counter(nums)   
        # 2-3. build heap of top k frequent elements and
        # convert it into an output array
        # O(N log k) time
        return heapq.nlargest(k, count.keys(), key=count.get) 

使用小根堆

  • 维护一个大小为k的小根堆,将所有的数以frequency的大小排序
  • 如果大小大于k,就pop出堆顶
  • 这样我们遍历完整个count map之后,剩下在堆里的元素,就是frequency最大的k个了

heapq.heappushheapq.heappop维护的都是最小堆

# Time complexity: O(Nlogk)
# Space complexity: O(N+k)
from collections import Counter
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        if k == len(nums):
            return nums
​
        count = Counter(nums)
​
        min_heap = []
        for key, freq in count.items():
            heapq.heappush(min_heap, (freq, key))
            # 维护一个长度为k的小根堆
            if len(min_heap) > k:
                heapq.heappop(min_heap)
​
        return [min_heap[i][1] for i in range(k)]

栈与队列总结

栈经典题目

栈在系统中的应用

  • 递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

括号匹配问题

这里有三种不匹配的情况,

  1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
  2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。
  3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。

字符串去重问题

把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。

逆波兰表达式问题

每一个子表达式要得出一个结果,然后拿这个结果再进行运算(将每个子表达式的result push进栈)

队列的经典题目

滑动窗口最大值问题

  • 主要思想是队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。 那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。

  • 设计单调队列的时候,pop,和push操作要保持如下规则:

    1. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
    2. push(value):如果push的元素value大于入口元素的数值,那么就将队列出口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

    保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。

  • 单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列。不要以为本地中的单调队列实现就是固定的写法。

求前 K 个高频元素

  • 使用大小固定为k的小根堆(优先队列)进行排序
  • 其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。而且优先级队列内部元素是自动依照元素的权值排列。
  • 堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
  • 所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
  • 本题就要使用优先级队列来对部分频率进行排序。 注意这里是对部分数据进行排序而不需要对所有数据排序!