1. 滑动窗口问题:利用双端队列实现自定义单调队列
虽然是叫滑动窗口,但和双指针的滑动窗口含义不太一样,双指针滑动窗口方法长度往往可变(最小覆盖子串等),这里的窗口长度不可变,因此更类似于队列
1.1 链接
https://leetcode.cn/problems/sliding-window-maximum/
1.2 关键知识点
- 暴力方法,遍历一遍的过程中每次从窗口中再找到最大的数值,这样很明显是O(n × k)的算法
- 这个进入滑动窗口的过程,类似队列,
- Deque(double ended queue简称,习惯上称之为双端队列)
- 既可以作为Stack使用(Last-In-First-Out,push\pop\peek在开头)
- 又可以作为Queue使用(First-In-First-Out)
使用实现Deque接口的类有ArrayDeque、LinkedList。如果要实现队列或双端队列数据结构,则ArrayDeque可能比LinkedList快
ArrayDeque<Type> animal = new ArrayDeque<>();
//如果使用Deque<String>声明队列,你可以更轻松地将队列实现切换为另一个实现类,而不必更改代码中的所有引用。这种做法可以使代码更加灵活和可维护。
Deque<Type> animal = new ArrayDeque<>();
1.3 自己遇到的细节问题
- 对栈java中是双端队列实现的这一点要理解
- 使用没有定义输入类型的类,应该是CustomQueue rsQueue = new CustomQueue();,没有<>
- 读取的方法使用前(如getLast和peek,都要有为空的判断)
- 为什么滑动窗口最大值不用优先级队列?该题不用排序窗口内所有的,只用把可能大的值加入队列,和队列口的元素比较就可以了
1.4 题解
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
CustomQueue rsQueue = new CustomQueue();
int m = nums.length;
int[] rs = new int[m - k + 1];
for(int i = 0;i < m; i ++){
rsQueue.add(nums[i]);//滑入
if(i >= k -1){//遍历满足k个就可以开始获取max
rs[i - k + 1] = rsQueue.peek();
rsQueue.poll(nums[i - k + 1]);//滑出
}
}
return rs;
}
}
class CustomQueue{
Deque<Integer> customQueue;
public CustomQueue(){
customQueue = new ArrayDeque<>();
}
void add(int x){
//如果不小于队列尾部,就加入到尾部。如果小于,移除尾部
while((!customQueue.isEmpty()) && x > customQueue.getLast()){
customQueue.removeLast();
}
customQueue.add(x);
}
//如果队列头部等于要加入的值就弹出
void poll(int x){
if((!customQueue.isEmpty()) && customQueue.peek() == x){
customQueue.poll();
}
}
int peek(){
return customQueue.peek();
}
}
2.前K个大数问题:优先级队列实现小顶堆
2.1 链接
2.2 关键知识点
- map计算频率(虽然很简单但自己总是不能很快写出来)
Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
- 如何创建一个小顶堆(min-heap)
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
//其中的元素是k-v二维数组
PriorityQueue<int[]> pq = new PriorityQueue<>((p1,p2) -> p1[1] - p2[1]);
- 遍历map Entry的kv的方法
//如果只是import java.util.* 注意迭代map 需要 for(Map.Entry<Integer,Integer> a :map.entrySet())
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
}
//或者通过entry的get(key),遍历key获取k-v
for (Integer i : Sites.keySet()) {
System.out.println("key: " + i + " value: " + Sites.get(i));
}
2.3 自己遇到的细节问题
- entry获得键值是 getKey(),getValue()
2.4 题解
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//1把频率读取map中
Map<Integer, Integer> map = new HashMap<>();
for(int i : nums){
map.put(i, map.getOrDefault(i,0) + 1); //注意拼写
}
//2创建小顶堆(规则)
PriorityQueue<int[]> pq = new PriorityQueue<>((p1, p2) -> p1[1] - p2[1]);
//3遍历map
for(Map.Entry<Integer, Integer> entry : map.entrySet()){ //注意Map.Entry<yaoxieleixing>
//如果小等于k,直接add入队列/小顶堆(logk的排序)
if(pq.size() < k){ //要求为k个,应该是< k,最后一个add完就是k了
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
//如果大于k
//如果大于堆顶
if(entry.getValue() > pq.peek()[1]){ //注意peek出来的结果是二维的
//poll堆顶后add入队列y二维数组 .add(new int[]{})
pq.poll();
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
//4输出小顶堆,此时堆顶为第k高,堆底为最高,顺着反向输出都行。每输出一个poll一个
int[] rs = new int[k];
for(int i = k -1;i >= 0;i --){//注意是等于0
rs[i] = pq.poll()[0]; //只需要key
}
return rs;
}
}
3.总结
3.1 基本知识
常用方法
双端队列和栈都可以用Deque实现。java.util.Deque双端队列来实现队列与栈的各种需求
- pop() 一般指弹栈
- poll()指出队
两者都是删除双端队列第一个元素:
pop() 方法等价于removeFirst(), 底层调用的是removeFirst()方法。
poll()方法等价于pollFirst() 方法,底层调用的是pollFirst()。
栈:
加入到双端队列开头:push
弹出双端队列开头:pop/poll
返回双端队列开头:peek
双端队列:
加入双端队列最后面:add
弹出双端队列最后面:removeLast
读取双端队列最后面:getLast
弹出、读取双端队列前面的方法和栈比较类似
判断空均为:isEmpty
3.2 基础知识
栈里面的元素在内存中是连续分布的么?
这个问题有两个陷阱:
陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布。
陷阱2:缺省情况下,默认底层容器是deque,那么deque的在内存中的数据分布是什么样的呢? 答案是:不连续的。
具体来说,Java中的Deque接口有两个标准实现:ArrayDeque和LinkedList。其中,ArrayDeque是使用循环数组实现的,而LinkedList是使用链表实现的。在这两个实现中,只有ArrayDeque是使用连续的内存块来存储元素的。而LinkedList则是由一系列节点(Node)通过指针相连来实现的,因此其内存不是连续的。
需要注意的是,虽然ArrayDeque使用了连续的内存块来存储元素,但是其实现方式并不是将所有元素存储在一个连续的内存块中。相反,它将元素分散存储在多个连续的内存块中,并使用循环数组的方式来实现Deque的操作。
在Java中:
Queue提供了迭代器来遍历它们的元素。
Queue<String> queue = new LinkedList<String>();
for (String q : queue) {
System.out.println(q);
}
Iterator it = queue.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}