代码随想录栈与队列part02|239. 滑动窗口最大值、347.前 K 个高频元素、总结
239. 滑动窗口最大值
思路
:
滑动窗口的移动过程很像一个队列(先进先出),滑动窗口向后移动一位,队列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 个高频元素
思路
:
两个难点:
- 如何求每个数值出现的频率
- 如何对频率进行排序并求前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]
栈与队列总结
- 栈与队列的底层实现
- 栈经典题目
括号匹配、字符串去重、逆波兰表达式 - 队列经典题目
滑动窗口最大问题、求前K个高频元素 - 通过求滑动窗口最大值,以及前K个高频元素介绍了两种队列:单调队列和优先级队列,这是特殊场景解决问题的利器,是一定要掌握的。