239. 滑动窗口最大值 (一刷至少需要理解思路)
本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。
单调队列-自己实现一个队列
import java.util.Deque; import java.util.LinkedList; //自定义单调队列 class MyQueue{ Dequedeque = new LinkedList<>(); //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出(=max弹出) //同时判断队列当前是否为空 void poll(int val){ if(!deque.isEmpty() && val == deque.peek()){ deque.poll(); } } //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出 //保证队列元素单调递减 //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2 void add(int val){ while (!deque.isEmpty() && val > deque.getLast()){ deque.removeLast(); } deque.add(val); } //队列队顶元素始终为最大值 int peek(){ return deque.peek(); } } //主函数 public class SlidingWindowMaximum { public int[] maxSlidingWindow(int[] nums, int k){ if(nums.length == 1){ return nums; } int len = nums.length - k + 1;// n - k + 1是滑块要滑动的次数 //存放结果元素的数组 int[] res = new int[len]; int index =0; //自定义队列 MyQueue myQueue = new MyQueue(); //先将前k的元素放入队列 for(int i = 0; i < k; i++){ myQueue.add(nums[i]); } res[index++]= myQueue.peek(); // 遍历数组 // 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点 // 队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出 for(int i= k; i < nums.length; i++){ //滑动窗口移除最前面的元素,移除是判断该元素是否放入队列 myQueue.poll(nums[i - k]); //滑动窗口加入最后面的元素 myQueue.add(nums[i]); //记录对应的最大值 res[index++] = myQueue.peek(); } return res; } }
347.前 K 个高频元素 (一刷至少需要理解思路)
1.堆(小顶堆)
思路(定义变量)
一个map来记录数组中值的出现频率
一个heap小顶堆,来对于最大的前K个元素排序
一个int[ ],用来接收数组中前k个最大的值
2. 本题思路分析:
通过map记录统计数组中各个元素的出现频率
一个heap小顶堆,来对于最大的前K个元素排序,
遍历数组中的所有元素,当小顶堆不满K个元素时,将数组当前元素直接加入堆中
当小顶堆存满K个元素后,每次加入元素都与堆顶元素作比较,大于堆顶元素将堆顶元素弹出堆,加入当前元素(小顶堆最小的元素在堆顶)
当数组便利完毕后,依次弹出堆顶元素,此时即为从小到大输出数组前K个最大的值,所以可以将数组从后往前接收堆弹出的值
import java.util.HashMap; import java.util.Map; import java.util.PriorityQueue; public class TopKFrequentElements { /*Comparator接口说明: * 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面 * 对于队列:排在前面意味着往队头靠 * 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆), * 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点 * */ //解法2:基于小顶堆实现 public int[] topKFrequent2(int[] nums, int k){ Mapmap = new HashMap<>();//key为数组元素值,val为对应出现次数 for(int num : nums){ //getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。 //map统计频率,key对应value从0开始,默认value++ map.put(num, map.getOrDefault(num,0)+ 1); } //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数 //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆) PriorityQueue pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);//实现compare函数 //for遍历map for (Map.Entry entry:map.entrySet()){ //小顶堆只需要维持k个元素有序 if(pq.size() pq.peek()[1]){ //当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个) pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了 pq.add(new int[]{entry.getKey(), entry.getValue()}); } } } int[] ans = new int[k];//定义一个返回数组 for(int i = k-1; i>=0;i--){//倒序遍历数组 //依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多 ans[i] = pq.poll()[0];//需要弹出是key } return ans;//返回结果 } }
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map map = new HashMap<>();
for(int num: nums){
map.put(num,map.getOrDefault(num,0)+ 1);
}
PriorityQueue pq = new PriorityQueue<>((pair1,pair2)->(pair1[1]-pair2[1]));
// Using entrySet() to get the entry's of the map
Set> s = map.entrySet();
for(Map.Entry entry: s){
// Using the getKey to get key of the it element
// Using the getValue to get value of the it element
if(pq.size()< k){
//堆里保持>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[] ans = new int[k];
for(int i = k -1; i>=0; i--){
ans[i] = pq.poll()[0];
}
return ans;
}
}
Console
//getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。
import java.util.*;
public class Test {
public static void main(String[] args) {
HashMap
map.put("张三", 1);
int a = map.getOrDefault("张三", 666);
int b = map.getOrDefault("王五", 666);
System.out.println(a);
System.out.println(b);
}
}
张三是已经存在的键值,返回了他对应的1(Value)
王五并不在map中存在,返回了默认值666(Value)
优先队列按照其作用不同,可以分为以下两种:
最大优先队列: 可以获取并删除队列中最大的值
最小优先队列: 可以获取并删除队列中最小的值
将元素放入队列:add,offer
将队首元素从队列删除:remove,poll
查看队列内的对首元素:element,peek
123
和标准队列不同的是,当删除队首元素的时候,删除的是priority queue中最小的元素。但是,priority queue并不是对所有的元素排序,其内部是用heap(堆)实现的。堆是一个自组织的二叉树,在这个二叉树里,add和remove操作使得最小的元素“吸引”到二叉树的根部,而不用在排列整个队列上耗费时间。
PriorityQueue
(n1,n2) -> n1-n2 //这一句加不加结果是一样的
);
heap.add(4);
heap.add(1);
heap.add(2);
heap.add(3);
while(!heap.isEmpty())
{
System.out.println(heap.poll());
}
123456789101112
输出顺序是1,2,3,4。
可以看到默认的比较规则就是n1-n2
,如果我们改成n2-n1
PriorityQueue
(n1,n2) -> n2-n1 //这一句加不加结果是一样的
);
123
输出顺序就是4,3,2,1。
可以想见优先队列内部是按照比较器返回的结果进行处理的,一般认为a,b比较结果大于0,就是a大于b,等于0就是a等于b;小于0就是a小于b。
现在我们知道当比较n1,n2的时候,如果结果大于0(也就是默认的n1-n2>0),那么要把n1下沉(默认的小根堆),当我们改成n2-n1的时候,结果大于0,证明n2大于n1,但还是n1(值较小的元素)下沉,于是这个堆就成了大根堆!
代码随想录算法训练营第13天 | LeetCode 239. 滑动窗口最大值,347.前 K 个高频元素,总结_XnyuDo的博客-CSDN博客