代码随想录算法训练营第13天
今日任务
● 239. 滑动窗口最大值 ● 347.前 K 个高频元素 ● 总结
链接:https://docs.qq.com/doc/DUHNpa3F4b2dMUWJ3 (opens in a new tab)
239. 滑动窗口最大值
题解想法
时间复杂度:O(n)
主要思想是队列没有必要维护窗口里的所有元素, 只需要维护有可能成为窗口里最大值的元素就可以了, 同时保证队列里的元素数值是由大到小的。
那么这个维护元素单调递减的队列就叫做单调队列, 即单调递减或单调递增的队列。
而且不要以为实现的单调队列就是对窗口里面的数进行排序, 如果排序的话,那和优先级队列又有什么区别了呢。
class MyDequeue {
// 双向队列,队尾存储最小值,队首存储最大值
Deque<Integer> queue = new LinkedList<Integer>();
// 窗口滑动,要移出元素时,比较当前要弹出的数值是否等于队尾的数值,如果相等则弹出
public void poll(int val) {
if(!queue.isEmpty() && val == queue.peek()) {
queue.poll();
}
}
// 窗口滑动,要加入元素时,从后往前移除比这个元素小的队列元素
// 保证了: 1.队列元素从头到尾递增;
// 2.比这个元素资历深但小的元素都被移除(这些元素以后永远不会是窗口最大值了);
// 3.移出元素时,由于(2.),可以保证弹出队首元素(记为a)后的下一个元素(记为b)一定还在窗口内,否则a加入的时候b就已经被移除了。
public void offer(int val) {
while(!queue.isEmpty() && val > queue.peekLast()) {
queue.pollLast();
}
queue.offer(val);
}
// 队首元素始终为最大值
public int peek() {
return queue.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) return nums;
int[] res = new int[nums.length - k + 1];
int res_num = 0;
MyDequeue queue = new MyDequeue();
// 先将前k的元素放入队列
for (int i = 0; i < k; i++) {
queue.offer(nums[i]);
}
res[res_num++] = queue.peek();
for(int i = k; i < nums.length; i++) {
queue.offer(nums[i]);
queue.poll(nums[i - k]);
res[res_num++] = queue.peek();
}
return res;
}
}回顾
-
java Deque声明:
Deque<Integer> queue = new LinkedList<>(); -
java Deque类的常用方法:
addFirst()、addLast()、removeFirst()、removeLast()、peekFirst()、peekLast()等。add/offer,element/peek,remove/poll中的三个方法均为重复的方法,区别在于前者在操作失败时会抛出异常,后者返回false或null。、
347.前 K 个高频元素
自己想法
用treeset排序
// 自定义类,实现Comparable接口,重写compareTo方法
class MyCount implements Comparable<MyCount>{
public int key;
public int value;
public MyCount(int key, int value) {
this.key = key;
this.value = value;
}
public int getKey() {return this.key;}
public int getValue() {return this.value;}
public int compareTo(MyCount x) {
// 这里的“等于返回1”,保证了相同value的元素不会被覆盖
if(x.getValue() >= this.value) return 1;
else return -1;
}
}
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// hashmap统计元素出现次数
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++) {
if(map.containsKey(nums[i])) map.put(nums[i], map.get(nums[i]) + 1);
else map.put(nums[i], 1);
}
// treeset排序
Set<MyCount> tree = new TreeSet<>();
// 此for复杂度O(n)
for(Map.Entry<Integer, Integer> e : map.entrySet()) {
// 此步复杂度O(logn)
tree.add(new MyCount(e.getKey(), e.getValue()));
}
// 取tree中前k
int[] ans = new int[k];
int i = 0;
for(MyCount e : tree) {
ans[i++] = e.getKey();
if(i == k) break;
}
return ans;
}
}题解想法
/*Comparator接口说明:
* 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
* 对于队列:排在前面意味着往队头靠
* 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
* 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
* */
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// hashmap统计元素出现次数
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++) {
if(map.containsKey(nums[i])) map.put(nums[i], map.get(nums[i]) + 1);
else map.put(nums[i], 1);
}
// (优先队列实现的)小堆来找前k大
// 小堆存储上述map的键值对
PriorityQueue<int[]> pq = new PriorityQueue<>((e1, e2) -> e1[1] - e2[1]);
// 此for复杂度O(n)
for(Map.Entry<Integer, Integer> e:map.entrySet()) {
// 先放k个
if(pq.size() < k) pq.offer(new int[]{e.getKey(), e.getValue()});
else {
// 若当前根小于e.getValue(),则剔除根,加入e
// 此步复杂度O(logk)
if(e.getValue() > pq.peek()[1]) {
pq.poll();
pq.offer(new int[]{e.getKey(), e.getValue()});
}
}
}
int[] ans = new int[k];
for(int i = 0; i < k; i++) ans[i] = pq.poll()[0];
return ans;
}
}
回顾
-
java PriorityQueue声明:
PriorityQueue<int[]> pq = new PriorityQueue<>((e1, e2) -> e1[1] - e2[1]); -
java HashMap的
entrySet()方法返回一个Set集合,Set集合中存储的是键值对对象Map.Entry<> -
java TreeSet声明:
Set<MyCount> tree = new TreeSet<>(); -
java 实现Comparable接口,以及TreeSet的
add()方法