239. 滑动窗口最大值
最主要的是构造【单调队列】,用于解决以下场景
给你一个数组 window
,已知其最值为 A
,如果给 window
中添加一个数 B
,那么比较一下 A
和 B
就可以立即算出新的最值;
但如果要从 window
数组中减少一个数,就不能直接得到最值了,因为如果减少的这个数恰好是 A
,就需要遍历 window
中的所有元素重新寻找新的最值。
为什么不选择使用优先级队列?
如果选择使用优先级队列,创建大(小)顶堆。可以更快的获取最值,但由于此题中,窗口是移动的,而大顶堆每次只能弹出最大值,无法移除其他数值,会造成大顶堆维护的不是滑动窗口里面的数值。
注:
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。
观察滑动窗口的过程可以理解,需要在头部和尾部进行数据操作,所以选择双链表数据结构
// 单调队列基本操作实现
class MonotonicQueue {
LinkedList<Integer> dequeue = new LinkedList<>();
public void push(int val){
// 将小于 val 的元素全部弹出删除
while(!dequeue.isEmpty() && dequeue.getLast() < val){
dequeue.pollLast();
}
// 最后将 val 添加入尾部
dequeue.addLast(val);
}
public void pop(int val){
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
if(!dequeue.isEmpty() && val == dequeue.getFirst()){
dequeue.pollFirst();
}
}
public int max(){
// 队列的队顶元素始终为最大值
return dequeue.getFirst();
}
}
剩下的便只需要模拟滑动窗口进行操作获取窗口内最大值:
完整代码如下:
// 单调队列基本操作实现
class MonotonicQueue {
LinkedList<Integer> dequeue = new LinkedList<>();
public void push(int val){
// 将小于 val 的元素全部弹出删除
while(!dequeue.isEmpty() && dequeue.getLast() < val){
dequeue.pollLast();
}
// 最后将 val 添加入尾部
dequeue.addLast(val);
}
public void pop(int val){
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
if(!dequeue.isEmpty() && val == dequeue.getFirst()){
dequeue.pollFirst();
}
}
public int max(){
// 队列的队顶元素始终为最大值
return dequeue.getFirst();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue window = new MonotonicQueue();
List<Integer> res = new ArrayList<>();
for(int i = 0;i < nums.length;i++){
// 首先填充滑动窗口前 k - 1 个元素,再向其中添加元素进行操作
if(i < k - 1){
window.push(nums[i]);
}else{
// 窗口开始进行滑动
// 添加新元素
window.push(nums[i]);
// 获取窗口最大值
res.add(window.max());
// 移除旧元素
window.pop(nums[i - k + 1]);
}
}
int[] arr = new int[res.size()];
for(int i = 0;i < res.size(); i++){
arr[i] = res.get(i);
}
return arr;
}
}
347.前 K 个高频元素
这道题目的本质主要涉及到如下三块内容:
- 要统计元素出现频率
- 对频率排序
- 找出前K个高频元素
统计元素出现频率 -> map来进行统计
排序则决定时间复杂度
该题我们选择堆排序,只需要维护k个排序数据使时间复杂度为O(nlogk)
完整代码如下:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer,Integer> valToFreq = new HashMap<>();
for(int v : nums){
valToFreq.put(v,valToFreq.getOrDefault(v,0) + 1);
}
PriorityQueue<Map.Entry<Integer,Integer>>
pq = new PriorityQueue<>((entry1,entry2) -> {
// 队列按照键值中对应的值从小到大排序
return entry1.getValue().compareTo(entry2.getValue());
});
for(Map.Entry<Integer,Integer> entry : valToFreq.entrySet()) {
pq.offer(entry);
if(pq.size() > k){
// 弹出最小元素,维护队列内是 k 个频率最大的元素
pq.poll();
}
}
int[] res = new int[k];
for(int i = k - 1;i >= 0;i--){
// res数组中存储前 k 个最大元素
res[i] = pq.poll().getKey();
}
return res;
}
}