leetcode刷题-栈与队列03

代码随想录栈与队列part02|239. 滑动窗口最大值、347.前 K 个高频元素、总结

239. 滑动窗口最大值

leetcode题目链接
代码随想录文档讲解

思路
滑动窗口的移动过程很像一个队列(先进先出),滑动窗口向后移动一位,队列pop掉前面元素,push进去一个元素,调用get_max_value函数。

优先级队列:大顶堆、小顶堆(C++),但是优先级队列会把元素顺序改变,不能使用

单调队列:相对于优先级队列,单调队列可实现单调递增或单调递减,但如何加元素和如何将元素pop出来的规则是由我们自定义的。
没有必要将3个元素都加进来,只需要维护有可能成为最大值的放在出口。如果出口处有比当前最大值小的元素,pop。push的规则:每push一个元素,要pop一个元素,但是那个元素可能已经被pop掉了,(当队列中的元素>滑动窗口大小,pop);如果push进来的元素比前面的元素都大,那前面的元素都要pop掉,保证最大元素在出口出。

伪代码c++
注意: 判断时是top,之后是pop

// 自定义队列
deque<int>  que;
pop(int val){
	if( ! que.empty() && val == que.fornt()) // 如果要pop的元素是队列出口处的元素,说明左边要遗弃的元素是最大值
		que.pop_front(); //这是一个双向队列
}

push(int val){
	if( ! que.empty() && cal > que.back())  // 加入的元素比队列中元素大,把队列中小的元素都pop
		que.pop_back();
	que.push_back(val);
}
get_max_value()
	return que.front();

python代码

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 个高频元素

leetcode题目链接
代码随想录文档讲解

思路

两个难点:

  1. 如何求每个数值出现的频率
  2. 如何对频率进行排序并求前k个高频元素
    -> map数据结构,时间复杂度高
    这里采用大顶堆和小顶堆(堆:二叉树)

遍历过后,堆里面只维护k个元素
选用小顶堆,因为在push进一个元素时,要先pop一个元素,在堆数据结构中,从堆顶开始pop,若采用大顶堆,每次都把频率最高的元素pop掉了。
在这里插入图片描述

伪代码(C++)

for(i=0; i< nums.size; i++)
	map[nums[i]]++;
// C++中可使用现成的数据结构,小顶堆的优先级队列
prioriy_queue<key,value>
cmp; //自定义排序代码
for(map:it){
	que.push(it);
	if(que.size>k)  que.pop();
}
vector<int> result
for(i=k-1; i>=0; i--){
	result[i] = que.top().first() // first获得元素
	que.pop()
}

python代码

Python中heapq模块
heappush(heap,item)建立大、小根堆
heapq.heappush()是往堆中添加新值,此时自动建立了小根堆
heapq.heappop(heap)
从 堆 弹出并返回最小的项目,保持堆不变。 如果堆为空,则会引发 IndexError。 要访问最小的项目而不弹出它,请使用 heap[0]。

代码示例
>>> a=[3,6,1]
>>> heapify(a)                  #将a变成堆之后,可以对其操作 heapq.heapfy()是以线性时间讲一个列表转化为小根堆
>>> heappop(a)
1
>>> b=[4,2,5]                   #b不是堆,如果对其进行操作,显示结果如下
>>> heappop(b)                  #按照顺序,删除第一个数值并返回,不会从中挑选出最小的
4
>>> heapify(b)                  #变成堆之后,再操作
>>> heappop(b)
2

#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        #要统计元素出现频率
        map_ = {} #nums[i]:对应出现的次数
        for i in range(len(nums)):
            map_[nums[i]] = map_.get(nums[i], 0) + 1
        
        #对频率排序
        #定义一个小顶堆,大小为k
        pri_que = [] #小顶堆
        
        #用固定大小为k的小顶堆,扫描所有频率的数值
        for key, freq in map_.items():
            heapq.heappush(pri_que, (freq, key))
            if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                heapq.heappop(pri_que)
        
        #找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        result = [0] * k
        for i in range(k-1, -1, -1):
            result[i] = heapq.heappop(pri_que)[1]
        return result
# 解法二
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # 使用字典统计数字出现次数
        time_dict = defaultdict(int)
        for num in nums:
            time_dict[num] += 1
        # 更改字典,key为出现次数,value为相应的数字的集合
        index_dict = defaultdict(list)
        for key in time_dict:
            index_dict[time_dict[key]].append(key)
        # 排序
        key = list(index_dict.keys())
        key.sort()
        result = []
        cnt = 0
        # 获取前k项
        while key and cnt != k:
            result += index_dict[key[-1]]
            cnt += len(index_dict[key[-1]])
            key.pop()

        return result[0: k]

栈与队列总结

  1. 栈与队列的底层实现
  2. 栈经典题目
    括号匹配、字符串去重、逆波兰表达式
  3. 队列经典题目
    滑动窗口最大问题、求前K个高频元素
  4. 通过求滑动窗口最大值,以及前K个高频元素介绍了两种队列:单调队列和优先级队列,这是特殊场景解决问题的利器,是一定要掌握的。
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值