好难啊,没想出来,看了题解动画演示的第一个,感觉讲的挺清楚的,附个看了题解之后写的代码和思路:
思路还是直接看动画演示把~动画演示 单调队列 239.滑动窗口最大值 - 滑动窗口最大值 - 力扣(LeetCode)
大概意思就是维护一个双端队列(单调队列)始终存储可能是滑动窗口最大值的元素下标,每次输出队列的队首元素。感觉这句话信息量有点大。。。
先说说为什么不能用大顶堆。一开始写的就是大顶堆(因为是从第二个题开始写的,就有点想套一下)。。然后错了。。看了输出才发现咋回事。。大顶堆会改变元素本身的位置,也就是说本来是
[1,3,-1,-3,5,3,6,7]
用了堆之后这组数的前三个顺序就变成了
[3,1,-1]
滑动窗口往下走第二个最大值就直接不对了。
第二点使用双端队列的原因是,存在元素需要从队尾出队的情况。具体看注释叭
双端队列可以用的api看这篇~Java中Deque双端队列的api总结_sqzr316的博客-优快云博客
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//存最终结果
int[] result = new int[nums.length - k +1];
//创建双端队列
LinkedList<Integer> list = new LinkedList<>();
//遍历数组元素,右指针指向滑动窗口右边界
for(int right = 0; right < nums.length; right++){
//队列非空且当前元素大于队尾元素,去掉队尾元素直到当前元素小于队尾元素
while(!list.isEmpty() && nums[list.peekLast()] < nums[right]){
list.pollLast();
}
//每次遍历都会将当前元素下标加入队列中
list.offerLast(right);
//计算滑动窗口左边界
int left = right - k + 1;
//如果队头元素超出左边界,队头元素出队
if(list.peekFirst() < left){
list.pollFirst();
}
//从产生第一个长度为k的滑动窗口开始,把队列中最大元素添加到result中
if(right + 1 >= k){
result[left] = nums[list.peekFirst()];
}
}
return result;
}
}
想法是统计每个数字出现的次数,把次数按照从大到小排好序,再取排序好的前k个元素,就是整数数组里出现频率前k高的元素了。
具体实现:用HashMap装数字(key)和该数字出现次数(value),对次数(value)进行排序,排序好的entrySet(包含映射关系的视图)键值对放在list集合中,最后把list集合的前k个entrySet中的key(最后要返回的数字)装到一个数组里返回就行了。
hashmap.getOrDefault(Object key, V defaultValue):返回 key 相映射的的 value,如果给定的 key 在映射关系中找不到,则返回指定的默认值。
Map中采用Entry内部类来表示一个映射项,映射项包含Key和Value,就是一个键值对。Java中Map的 entrySet() 详解以及用法(四种遍历map的方式)_NO0b的博客-优快云博客
map.entrySet() 方法返回映射中包含的映射的 Set 视图。注意:Set 视图意思是 HashMap 中所有的键值对都被看作是一个 set 集合。
Collections类中的sort方法可以实现对List接口的集合进行排序,但不能对键值对中的值进行排序。因此想要比较value的大小,要重写sort方法中的Comparator接口的compare方法,格式:
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
关于sort方法可以参考这篇~【Java】Collections.sort() 方法 —— Comparable、Comparator接口_sort方法是哪个接口_杨丹的博客的博客-优快云博客
HashMap排序可以参考这篇~Java HashMap按key排序和按value排序的两种简便方法_hashmap没有顺序,要让映射关系根据键排序,使用哪种映射类呢? 来自课件““讨论3”_xHibiki的博客-优快云博客
下面是用lambda表达式化简过的
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//先把数全放到map集合中,统计一个数出现了几次
Map<Integer,Integer> map = new HashMap<>();
for(int i: nums){
map.put(i, map.getOrDefault(i,0)+1);
}
//对次数,即value进行排序,装到list集合中
List<Map.Entry<Integer,Integer>> list=new ArrayList<>();
list.addAll(map.entrySet());
Collections.sort(list,(o1, o2)->o2.getValue()-o1.getValue());
//取list前k个就是出现频率最高的元素
int[] result = new int[k];
for(int i = 0; i < k; i++){
result[i] = list.get(i).getKey();
}
return result;
}
}
但是时间不怎么理想就是说。。执行用时:13 ms, 在所有 Java 提交中击败了45.95%的用户
题解是用了堆的思想,始终维护一个含k个元素的小顶堆,如果元素比堆顶元素小直接舍弃,因为此时该元素肯定不是出现次数前k的元素;如果元素比堆顶元素大,则加入小顶堆,同时堆顶元素出堆。
堆在java中可以用PriorityQueue类(优先队列)实现,其中构建小顶堆不需要重写Comparator接口的compare方法,如果要构建大顶堆,则需要在compare中返回参数2-参数1的值。
//小顶堆
Queue<>() q1 = new PriorityQueue<>();
//大顶堆
Queue<>() q2 = new PriorityQueue<>((x, y) -> (y - x));
//同上
Queue<Integer> q2 = new PriorityQueue<>(k, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b){
return b - a; //大顶堆:参数2-参数1;小顶堆:参数1-参数2
}
});