代码随想录Day13:栈与队列Part3

Leetcode 239. 滑动窗口最大值

讲解前:

这里看到是一道困难题之后其实有点被吓到,但是看到了题目的要求后立马就能想到暴力解法,只需要每一次都遍历一遍窗口找最大值就可以了,但是不用想肯定会超时,所以决定直接看卡哥解说

讲解后:

看完了卡哥的讲解以后呢对这个单调队列的理解清晰了很多,关键点就在于我们要明白虽然我们的单调队列中并不是每次都存放着窗口中的k个元素,我们保证的其实是当前窗口中的最大值一定是存放在我们的队列中的,这样可以帮助我们解决在我们的窗口移动的时候如何pop掉那些小的不重要的数字,因为他们本来就不会存放进来

就像我这张图中画的一样,我们可以发现我们的窗口其实这时候只移动了两次,最一开始的时候我们就像计划好的一样,当5被加入queue之后,2,3就自动消失了,通过我们的deque数据结构的pop() 操作,因为就像我说的一样,我们要保证queue中的队首保存的永远是当前窗口中的最大值,接下来我们的窗口还是会正常的一个一个往后移动,意味着新的窗口中2将会消失,这时我们检查这个要消失的2是不是我们队首的数字,发现并不是,所以我们自己的队列其实不需要做出任何改变,因为当前队列中 (3, 5, 1) 还包含着最大值5, 然后我们把1加入队列,因为哪怕1这时候还是比5小,但是他是最新的值,我们没办法确定他后面的值还会不会有比他大的,当窗口再次移动之后,3被pop掉,依然不用管pop我们用的queue,因为在窗口(5, 1, 4) 中,5依然是最大的那一个,并且这时候,随着4的加入,1就被消除了,因为既然4是在1之后新加入的,那么只要窗口往后移动,4一定会在自己被pop掉之前至少一定比1要大,1反正也要比4提前被窗口的移动正常pop掉,没有必要保留1,这就是我们代码中每一次push的时候要一直清理比当前的数字小的值的原因

接下来虽然在图中没画完,但是我们知道窗口继续移动就会变成(1,4,2)也就是要把5 pop掉,这时我们发现确实新的窗口中要把5删除了而且5确实是在我们的queue的队首,这就意味着5当老大的时间结束了,他在我们的queue中要被pop掉了,然后紧接着4就变成了新的队首,4也确实是当前窗口中最大的值

class Solution:
    class MyQueue:
        def __init__(self):
            self.queue = deque()
        
        def pop(self, value):
            if self.queue[0] == value:
                self.queue.popleft()
            
        def push(self, value):
            while self.queue and self.queue[-1] < value:
                self.queue.pop()
            self.queue.append(value)
        
        def get_max(self):
            return self.queue[0]
        
        def __str__(self):
            return str(self.queue)
    
    
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        q = self.MyQueue()
        res = []

        # add the all the elements in the first window first
        for i in range(k):
            q.push(nums[i])
        
        res.append(q.get_max())

        for start in range(0, len(nums) - k):
            end = start + k 
            q.push(nums[end])
            q.pop(nums[start])
            res.append(q.get_max())
        
        return res

Leetcode 347. 前K个高频元素

讲解前:

在做这道题之前看了一样简介发现自己已经完全忘记了关于堆的相关知识,也从来没有用过python每部的堆模块,于是决定先去复习一下,然后了解了堆必须是一个完全二叉树然后还有优先级队列和堆的关系

当然这道题也有暴力解法,就是直接把数组排序然后取前k个unique的数字就可以了,或者稍微更聪明一点的解法可以是我们用一个map来储存每个数字出现的次数然后对values进行排序,再返回出现最多次数的k个key,对于堆的应用我脑海里想的就是我们自己定义一个优先队列然后队列中排队的优先级不再是数字的大小而是其出现的频率

讲解后:

看完了讲解之后我发现自己对堆的理解还不够深刻,其实对于这种堆的题,更应该联想到的就是priority queue,堆只是一个实现方式,这里我们的解法其实还是要利用map先计算好每个数字出现的次数,只不过我们接下来通过一次遍历来解决问题,而不是对map进行排序,我们利用一个priority queue用来保存k个元素,也就是说因为字典是无序的所以我们遍历的过程中有可能要push元素到我们的queue中去然后再pop掉元素,所以我们用一个 min heap,根节点永远保存的是最小的值也就是是出现次数最少的键值对,这样每一次当我们遍历到多余k的时候,我们的堆就会帮助我们自动用pop函数去把出现次数最小的推出,这样以来等我们遍历结束之后,整个queue里保存的就是出现次数最多的k个值

这里我们可以聊一些python中heapq的问题,首先就是在这道题中,我们要注意我们在用heappush的时候里面添加的元素,我们的目的是让priority queue根据每个数字出现的次数来维持堆的结构,然后呢在我们的map中,出现的次数是用value来存的,所以我们把key value pair push进去的时候要保证是这样的 (value, key),value需要在key的前面,因为python对于这种类型的input,总是会先按照第一个index来排序。

如果遇到第一个index是一样的情况,python会继续往后找,按照第二个index来排序,也就是说如果我们新建一个priority queue然后用heapq push (1, 3, 2) 和 (1, 2, 4),那么我们的堆顶存放的元素就会是 (1, 2, 4) 因为python发现这两个tuple的index 0都是1了之后就去看index1,然后发现不同了之后就按index 1 来排

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # count each number's appearance first
        count_map = {} 
        for i in range(len(nums)):
            count_map[nums[i]] = count_map.get(nums[i], 0) + 1

        # create a list which will be iterated by heapq
        # and become a priority queue
        pri_queue = []

        # iterate through the map and add to the pri queue
        for key, value in count_map.items():

            # push each kv pair to the min priority queue
            heapq.heappush(pri_queue, (value, key))
    
            # only keep k elements in the queue
            # alway pop the root every time we push new one after size is bigger than k
            if len(pri_queue) > k:
                heapq.heappop(pri_queue)

        # add the value of kv pair to the res list
        res = []
        for value, key in pri_queue:
            res.append(key)
        
        return res

然后呢这种解法的时间复杂度就变成了nlogk,因为我们依然需要遍历每一个数组中的数字,但是当我们进行添加到queue中的操作时,每一个push的操作取决于当前heap中的元素数量,然后呢我们保证priority queue的大小是k所以每一次都是logk

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值