文档讲解:代码随想录(代码随想录)
完成状态: true
239. 滑动窗口最大值
最开始只能想到暴力解法(但是由于时间复杂度的原因提交失败):
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] result = new int[nums.length - k + 1];
for(int i = 0; i < nums.length - k + 1; i++){
int temp = Integer.MIN_VALUE;
for(int j = i; j < i + k; j++){
if(nums[j] > temp){
temp = nums[j];
}
result[i] = temp;
}
}
return result;
}
}
优先级队列为什么不行?因为该队列在pop出元素后,可能pop出参与下一个滑动窗口比较的值,所以它会影响元素的顺序。其不能按照滑动窗口的需求去按序pop。
假如有一个队列,可以向里面添加和删除元素,并且可以返回当前队列的最大值。那么这道题我们就可以使用这个队列,固定其长度,每次向队尾里面添加一个元素,同时删除队头的元素,此时返回当前队列的最大值即可。但此处的和上述的优先级队列不一样的地方是,其每次pop元素都是不会影响元素的原顺序的,这也是难点,需要我们考虑要如何实现的点。
故而需要我们自己定义一个单调队列,可以满足我们的需要,维护队列里面的元素单调递减。代码中重点代码体现在push()处,重点理解。
class MyQueue{
private Deque<Integer> deque = new LinkedList<>();
// 弹出元素
public void pop(int value){
if(!deque.isEmpty() && value == deque.peek()){
deque.poll();
}
}
// 添加元素时,如果添加的元素大于队列前面的元素,则将比自己小的元素都弹出
// 保证队列元素单调递减
public void push(int value){
while(!deque.isEmpty() && value > deque.getLast()){
deque.removeLast();
}
deque.add(value);
}
// 查看当前队列中元素的最大值(对于我们维护的队列,最大元素就是对头元素)
public int peek(){
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] result = new int[nums.length - k + 1];
int j = 0;
MyQueue myQueue = new MyQueue();
for(int i = 0; i < k; i++){
myQueue.push(nums[i]);
}
result[j++] = myQueue.peek();
for(int i = 1; i < nums.length - k + 1; i++){
myQueue.pop(nums[i - 1]);
myQueue.push(nums[i + k - 1]);
result[j++] = myQueue.peek();
}
return result;
}
}
347. 前 K 个高频元素
前置知识:
- 优先级队列:优先队列也是一种队列,不同的是,优先队列的出队顺序是按照优先级来的。对于优先队列,元素进入队列的顺序可能与其被操作的顺序不同,作业调度是优先队列的一个应用实例,它根据优先级的高低而不是先到先服务的方式来进行调度。
函数名 功能介绍 boolean offer(E e) 插入元素 e,插入成功返回 true,如果 e 对象为空,抛出 NullPointerException 异常,时间复杂度为 O(log2N) ,注意:空间不够时会自动扩容 E peek() 获取优先级最高的元素,如果优先级队列为空,返回 null E poll() 移除优先级最高的元素并返回,如果优先级队列为空,返回 null int size() 获取有效元素的个数 void clean() 清空 boolean isEmpty() 检测优先级队列是否为空,空返回 true 在Java中也实现了自己的优先队列
java.util.PriorityQueue
,Java中内置的为最小堆,底层维护了一个Object类型的数组。如果想要把最小堆变成最大堆可以给PriorityQueue传入自己的比较器。
- 堆:一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。如果是大顶堆,那么其根节点一定是最大的(记住大根堆构建完并不代表是有序的)。
大根堆:适合求前k个最小值
小根堆:适合求前k个最大值
getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。(hashmap.getOrDefault(Object key, V defaultValue))
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int num: nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}
// 使用优先级队列对频率的值进行排序
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]); // 按照value进行排序
Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
for(Map.Entry<Integer, Integer> entry: entrySet){
pq.add(new int[]{entry.getKey(), entry.getValue()});
// 如果小根堆超过了k个元素
if(pq.size() > k){
pq.poll();
}
}
int[] res = new int[k];
for(int i = k - 1; i >= 0; i--){
res[i] = pq.poll()[0];
}
return res;
}
}
215. 数组中的第K个最大元素
有上题的铺垫,这道就很容易了。但此处并不是最优解法。
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>((a,b)->b-a); // 大根堆
for(int num: nums){
pq.add(num);
}
int result = -1;
for(int i = 0; i < k; i++){
if(i == k -1){
result = pq.poll();
break;
}
pq.poll();
}
return result;
}
}