Day13
前言
新的一周又开始了,提高效率,继续加油
文章:代码随想录
LeetCode 239 滑动窗口最大值
自己思路
看了看题目,只想得到暴力思路,听从建议,先看视频
看完讲解
看视频看的云里雾里很糊,感觉没有表述清楚,快看完了这一集才发现原来所谓的弹出和卷出去是两个意思,太口语化了,没懂是两个方向的弹出元素,看老半天看不懂,后面重新看了下文字版的讲解和评论区,感觉清晰多了,可以看看这个视频前半段的讲解和动画,清晰一些【单调队列 滑动窗口最大值【基础算法精讲 27】】
简单的概括就是单调队列,维护一个单调队列来对窗口进行管理,单调队列具有加入元素、弹出元素、获取最大值三个功能,窗口每移动一次,就调用这三个功能。加入元素,每加入一个元素,就和队列中的前一个元素(靠近入口的)进行比较,如果比他大,那么就把前一个元素弹出,一直比较到前一个元素大于自己了,再加入自己。弹出元素,每移动一次窗口,就将窗口漏去的值和靠近出口的元素进行比较,如果相同就弹出该元素,如果不同就不进行任何操作。这样下来,就保证了这个队列是单调的,最大的元素永远在出口处,所以最后的获取最大值功能,直接返回出口处元素即可。现在思路清晰了,但是代码写起来并不容易,所以是边看边写的,今天对Deque的总结也花了很多时间,gpt有时候和傻子一样
首先是要实现自己的单调队列类,牢记入口为Last,出口为First。首先需要注意的是,当获取栈内元素的时候,要在前面加上非空才能去获取。然后在push判断的时候,val大于当前值的时候才poll,等于的时候不poll,如果等于也poll会出现在push的时候poll一次,然后poll判断又相等了,会多poll一次从而出错。然后在Solution类中,记得是去实现这个单调队列类,返回的数组长度可以求出来,用index来控制该数组的索引。然后第一遍for循环是指加入第一组k个元素,此时i指的滑动窗口的起点,只需要进行add最后再peek就行。第二遍for循环i指的就是滑动窗口的终点了,每一次都依次执行poll、add、peek操作。最后循环结束了返回整个数组就行
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] result = new int[nums.length - k + 1];
int index = 0;
MyQueue myqueue = new MyQueue();
for (int i = 0; i < k; i++) {
myqueue.add(nums[i]);
}
result[index] = myqueue.peek();
index++;
for (int i = k; i < nums.length; i++) {
myqueue.poll(nums[i - k]);
myqueue.add(nums[i]);
result[index] = myqueue.peek();
index++;
}
return result;
}
}
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
public void poll(int val) {
if (!deque.isEmpty() && deque.peekFirst() == val) {
deque.pollFirst();
}
}
public void add(int val) {
while(!deque.isEmpty() && val > deque.peekLast()) {
deque.pollLast();
}
deque.addLast(val);
}
public int peek() {
return deque.peekFirst();
}
}
LeetCode 347 前 K 个高频元素
自己思路
没有一个比较好的思路,去看视频
看完讲解
主要的思路就是采用优先级队列小顶堆,整个过程中遇到了非常多不懂的,恶补了很多知识,最后基本上是边看答案边写出来的,很多语言都不熟练,但是思路基本清楚了。我认为关键的就是两步,第一步是统计频率,这里借用HashMap。第二步就是维护前K个高频元素,这里利用的优先级队列的小顶堆,因为小顶堆每次都是弹出堆顶的最小的元素,这样子剩下的就是较大的元素了,所以用小顶堆,只需要保证小顶堆的元素是k个即可,如果不足k个直接加入,如果多于k个,就和最小元素比较,比他大那就加入,并且优先级队列PriorityQueue的add方法还会直接将元素添加到适当位置,就非常完美,所以关于是否需要add我们就只需要和最小的来比较就行,后面的就交给小顶堆自己维护
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 利用HashMap统计频率
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num,0)+1);
}
// 利用小顶堆维护k个元素(弹出的都是堆顶的小元素,留下的就是大的高频元素)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
// 不足k个直接加
if (pq.size() < 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[] result = new int[k];
for (int i = k-1 ; i >= 0; i--) {
// 堆顶元素频率最低,排在后面
result[i] = pq.poll()[0];
}
return result;
}
}
总结
用时:近8h,遇到难题就感觉看视频很糊,恶补很多知识盲区
今天花了很多时间又对Deuqe进一步探索了,更新到Day10了;还研究了一下HashMap,更新到Day6了;其余和本节更相关的内容更新在下面了
增强for循环(for-each循环)
可以用来遍历任何实现了Iterable接口的对象,包括所有的Collection框架(如数组、集合,HashSet等),简化遍历过程
// 遍历数组
int[] arr = {1, 2, 3, 4, 5};
for(int num : arr) {
System.out.println(num);
}
优先级队列(大顶堆/小顶堆)的定义和操作
优先级队列其实就是一个披着队列外衣的堆,所以优先级队列的操作和队列的操作就基本一致
但是关于为什么这样定义就是小顶堆,那样定义就是大顶堆,这个Lambad表达式还是不懂
// 优先级队列(大顶堆)的定义
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1]-pair1[1]);
// 优先级队列(小顶堆)的定义
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
// 优先级队列加入元素——根据优先级添加到适当的位置
pq.add(x);
// 优先级队列移除并返回优先级最高的元素
pq.poll();
// 优先级队列返回优先级最高的元素
pq.peek();
// 队列大小
pq.size();
// 队列空否
pq.isEmpty();