239. 滑动窗口最大值
思路:
-
暴力法:遍历各个滑动窗口,从中取出最大值(O(n × k)超出时间限制)
-
单调队列法:设计单调队列的时候,pop() 和 push() 操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调队列的出口元素(维护的最大值),那么队列弹出元素,否则不用任何操作;
- push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素逐个弹出,直到push元素的数值小于等于队列入口元素的数值为止。(注意是从后往前poll,保证队列递减)
保持如上规则,每次窗口移动的时候,只要 que.front() 就可以返回当前窗口的最大值。为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,动画如下:
首先要明确的是,题解中单调队列里的pop和push接口,仅适用于本题哈。单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列。 不要以为本题中的单调队列实现就是固定的写法。
// 1. 暴力法:超出时间限制
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayList<Integer> ans = new ArrayList<Integer>();
int left=0, right=k-1;
while(right < nums.length) {
int max = Integer.MIN_VALUE;
for(int i=left; i<=right; i++) {
max = max > nums[i] ? max : nums[i];
}
ans.add(max);
left++;
right++;
}
int[] res = new int[ans.size()];
int index = 0;
for(Integer n: ans) {
res[index++] = n;
}
return res;
}
}
// 2. 单调队列法:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] ans = new int[nums.length - k + 1]; // 滑动窗口数为结果数组大小
Deque<Integer> dq = new LinkedList<Integer>();
//将第一个滑动窗口存入单调队列
for(int i=0; i<k; i++) {
while(!dq.isEmpty() && nums[i]>dq.peekLast()) {
//注意是从后往前维护,这也是为什么要用到双端队列
dq.pollLast();
}
dq.offerLast(nums[i]);
}
// 存入第一个窗口最大值
int index = 0;
ans[index++] = dq.peekFirst();
//窗口开始向后移动
for(int j=k; j<nums.length; j++) {
// 若窗口要移除元素正好是单调队列出口的元素(所维护的最大值),将其移出队列
if(dq.peekFirst() == nums[j-k]) dq.pollFirst();
// 新元素存入,并维护单调队列
while(!dq.isEmpty() && nums[j]>dq.peekLast()) {
dq.pollLast();
}
dq.offerLast(nums[j]);
ans[index++] = dq.peekFirst();
}
return ans;
}
}
347.前 K 个高频元素
思路:
- 利用Map存储:key为元素值,value对应出现次数,然后对map中元素按照value值排序取前k个即可。注意自定义排序的写法
- 利用优先级队列:其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
// 方法一:HashMap
class Solution347_ {
public int[] topKFrequent(int[] nums, int k) {
int[] ans = new int[k];
Map<Integer, Integer> mp = new HashMap<Integer, Integer>();
// 存入map,key为元素值,value对应出现次数
for(int n: nums) {
mp.put(n, mp.getOrDefault(n, 0) + 1);
}
//对map中元素按照value值排序并存入list中
List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer,Integer>>(mp.entrySet());
Comparator<Map.Entry<Integer, Integer>> valueComparator = new Comparator<>() {
@Override
//compare方法,当方法的返回值大于0的时候就将列表的前一个数o1和后一个数o2做交换
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
// 倒序排序后减前
return o2.getValue() - o1.getValue();
}
};
Collections.sort(list, valueComparator);
for(int i=0; i<k; i++) {
ans[i] = list.get(i).getKey();
}
return ans;
}
}
// 方法二:优先级队列
class Solution {
//解法1:基于大顶堆实现
public int[] topKFrequent1(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从大到小排,出现次数最多的在队头(相当于大顶堆)
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[] ans = new int[k];
for(int i=0;i<k;i++){//依次从队头弹出k个,就是出现频率前k高的元素
ans[i] = pq.poll()[0];
}
return ans;
}
总结
自定义排序
利用 Collections.sort() 方法,传入 List 和 Comparator对象两个参数,注意要重写Comparator对象中的compare方法。
- 注意:若要调用compareTo,必须是包装类型,若为int则需要Integer.valueOf()转为Integer。
- 注意:建议使用compareTo而不是直接减。eg.若被减数为int最小值-2147483648,减去1则会变成int的最大值2147483647,会出错的!!!
//对Map中元素按照value值倒序排序并存入list中
System.out.println(mp);// {1=3, 2=1, 3=6, 4=2, 5=4, 6=5}
//注意存入List中的写法
ArrayList<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>>(mp.entrySet());
Comparator<Map.Entry<Integer, Integer>> valueComparator = new Comparator<Map.Entry<Integer, Integer>>() {
@Override
//compare方法,当方法的返回值大于0的时候就将列表的前一个数o1和后一个数o2做交换
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
//注意此处o1与o2位置
//指定的数与参数比较
//相等返回0; 小于参数返回 -1; 大于参数返回 1
return o2.getValue().compareTo(o1.getValue());
//return o2.getValue()-o1.getValue();
}
};
Collections.sort(list, valueComparator);
System.out.println(list); // [3=6, 6=5, 5=4, 1=3, 4=2, 2=1]
● 利用优先队列
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序从大到小排,出现次数最多的在队头(相当于大顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o2[1] - o1[1]);
for(Map.Entry<Integer,Integer> entry: mp.entrySet()){//存入优先队列
pq.add(new int[]{entry.getKey(), entry.getValue()});
}
for(int i=0; i<mp.size(); i++) {
System.out.print(Arrays.toString(pq.poll())+" ");
} // [3, 6] [6, 5] [5, 4] [1, 3] [4, 2] [2, 1]