一、滑动窗口最大值
题目:滑动窗口最大值
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
思路:
1.创建一个单调队列
创建一个队列类如下:
public class MyQueue {
Deque<Integer> deque=new LinkedList<>();
//重写poll方法,当val==deque.peek()队列最大值的时候,才弹出
void poll(int val) {
if(!deque.isEmpty()&&val==deque.peek())
deque.poll();
}
//当val大于队列的最小值时,弹出最小值,放入val
void add(int val) {
while(!deque.isEmpty()&&val>deque.getLast())
deque.pollLast();
deque.add(val);
}
//队列队顶元素始终为最大值
int peek() {
return deque.peek();
}
}
接下来遍历nums,先将前K个元素依此放入单调队列中,然后取得一个最大值,依此遍历剩余数组中的值,如果滑动窗口移除的值为单调队列的最大值,则弹出,如果添加进滑动窗口的值大于最后一个值,则替换,依此得出单调队列中的最大值。
代码如下:
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length==1)
return nums;
//滑动窗口最大值数组的长度
int len=nums.length-k+1;
//结果数组
int[] res=new int[len];
int num=0;
//创建一个单调队列
MyQueue myQueue=new MyQueue();
for (int i = 0; i < k; i++) {
//先加入前K个数
myQueue.add(nums[i]);
}
//取最大值
res[num++]=myQueue.peek();
for (int i = k; i < nums.length; i++) {
//判断滑动窗口移除的值,是否是最大值,是则弹出
myQueue.poll(nums[i-k]);
//添加nums[i]
myQueue.add(nums[i]);
//取最大值
res[num++]=myQueue.peek();
}
return res;
}
2.双端队列实现单调队列
单调队列中存的是地址,见代码。
public int[] maxSlidingWindow(int[] nums, int k) {
//创建一个双端队列
ArrayDeque<Integer> deque=new ArrayDeque<>();
int n=nums.length;
//结果数组
int[] res=new int[n-k+1];
int num=0;
for (int i = 0; i < n; i++) {
//当单调队列中的数不在滑动窗口中,就弹出
while(!deque.isEmpty()&&deque.peek()<i-k+1){
deque.poll();
}
//当i位置上的数大于单调队列中最后一个位置上的数在nums的值,弹出最后一个数
while(!deque.isEmpty()&&nums[deque.getLast()]<nums[i]){
deque.pollLast();
}
//单调队列存的是地址
deque.offer(nums[i]);
if(i>=k-1){
//取最大值
res[num++]=nums[deque.peek()];
}
}
return res;
}
二、前K个高频元素
题目:前K个高频元素
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]
思路:
利用map存储元素(作为key),及其出现的次数(作为value),暴力解法就是直接由大到小排序,输出前K的元素(快排的时间复杂度O(n*logn))。
再就是利用大根堆和小根堆,具体操作见代码。
代码如下:
//利用大根堆
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map=new HashMap<>();
//元素及其出现次数作为键值对存到map中
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//大根堆
PriorityQueue<int[]> pq=new PriorityQueue<>((pair1,pair2)->pair2[1]-pair1[1]);
//依此添加到大根堆
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
int[] res=new int[k];
//依此输出前K个元素(因为大根堆根据出现次数的大小,最大的为头)
for (int i = 0; i < k; i++) {
res[i]=pq.poll()[0];
}
return res;
}
//利用小根堆
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map=new HashMap<>();
//元素及其出现次数作为键值对存到map中
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//小根堆
PriorityQueue<int[]> pq=new PriorityQueue<>((pair1,pair2)->pair1[1]-pair1[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
//当小根堆大小小于k时直接加入
if(pq.size()<k)
pq.add(new int[]{entry.getKey(),entry.getValue()});
else{
//当此时的数值大于小根堆的数,就弹出头,加入此时的元素及其出现此数
if(entry.getValue()>pq.peek()[1]){
pq.poll();
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] res=new int[k];
//小根堆,最小的数在头,因此倒序输出
for (int i = k-1; i >=0 ; i--) {
res[i]=pq.poll()[0];
}
return res;
}
总结:
栈和队列的题到此练完,只有优先队列的地方有些不懂,并且今天练习的两道题需要多刷几遍,继续加油!