代码随想录算法训练营第十二天|LeetCode239,347
239. 滑动窗口最大值
主要思想是队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列为单调队列。
设计单调队列的时候,pop,和push操作要保持如下规则:
- poll(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- add(value):如果push的元素add大于入口元素的数值,那么就将队列出口的元素弹出,直到add元素的数值小于等于队列入口元素的数值为止
保持如上规则,每次窗口移动的时候,只要问que.peek()就可以返回当前窗口的最大值。
其次要明确的是,题解中单调队列里的pop和push接口,仅适用于本题。
class MyDeque{
//使用Deque来实现单调序列
ArrayDeque<Integer> que = new ArrayDeque<>();
//每次弹出的时候,比较当前弹出的数值是否等于队列出口处的元素
//记得弹出的时候判断队列是不是为空
void poll(int val){
if (!que.isEmpty()&&val == que.peek()){
que.poll();
}
}
//添加元素的时候,如果要添加的元素大于入口处的元素,就将入口处的元素弹出
//保证单调队列单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
void add(int val){
while (!que.isEmpty()&&val>que.getLast()){
que.removeLast();
}
que.add(val);
}
int peek(){
return que.peek();
}
}
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1){
return nums;
}
// ArrayList<Integer> res = new ArrayList<>();
//计算最后结果数组的长度,可以用ArrayList
int len = nums.length - k + 1;
int[] res = new int[len];
//结果数组的下标
int num = 0;
//自定义队列
MyDeque myQue = new MyDeque();
//先将前K个元素放入自定义队列
for (int i = 0; i< k;i++){
myQue.add(nums[i]);
}
res[num++] = myQue.peek();
for (int i = k;i<nums.length; i++){
//滑动窗口移除最前面的元素,同时判断是否需要将队列中的第一个弹出
myQue.poll(nums[i-k]);
//将滑动窗口的最后一个元素加入单调队列
myQue.add(nums[i]);
//记录对应的最大值
res[num++] = myQue.peek();
}
return res;
}
347. 前 K 个高频元素
遍历数组,然后新建HashMap来记录每个值出现的频率,然后排序返回题目要求的前K个最大值
比较难实现的地方在于,排序具体怎么实现。参考题解对频率进行排序可以使用一种优先级队列(底层实现为堆)。
如果使用大顶堆的话,就需要将所有元素进行排序
,那能不能只排序k个元素呢?
定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。
参考题解用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
public int[] topKFrequent(int[] nums, int k) {
//Key为数组元素值,val为该元素出现的次数
HashMap<Integer,Integer> map = new HashMap<>();
for(int num:nums){
map.put(num, map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(小顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
for (Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆维持k个元素有序
if (pq.size()<k){//小顶堆元素个数小于k时直接加
pq.add(new int[]{entry.getKey(), entry.getValue()});
}else {
if (entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根节点(这k个元素中出现次数最少的那个)
pq.poll();//弹出队头,即把堆里出现次数最少的那个删除,留下的即出现次数多的
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] ans = new int[k];
for(int i = k-1;i>=0;i--){
//依次弹出小顶堆,先弹出的根,出现的次数少,后面弹出的出现次数越多
ans[i] = pq.poll()[0];
}
return ans;
}