借助自定义数据结构单调队列,理解思路,动图如下:
class MyDeque {
//自定义数据结构实现
Deque deque = new LinkedList<>();
//弹出元素
//若当前要弹出的数值等于队列出口数值,则弹出
void poll(int val){
if(!deque.isEmpty() && val == deque.peek()){
deque.poll();
}
}
//添加元素
//若要添加的元素大于入口处元素,就将入口处元素弹出,再添加
//从而保证队列内元素递减
void add(int val){
while(!deque.isEmpty() && val > deque.getLast()){
deque.removeLast();
}
deque.add(val);
}
//队列队首元素始终为最大值
int peek(){
return deque.peek();
}
}
class Solution{
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 1){
return nums;
}
int len = nums.length - k + 1;
//存放结果元素的数组
int[] result = new int[len];
int num = 0;
// 使用自定义数据结构
MyDeque mydeque = new MyDeque();
//将前k的元素放入队列
for(int i = 0;i < k;i ++){
mydeque.add(nums[i]);
}
result[num++] = mydeque.peek();
for(int i = k;i < nums.length;i++){
//滑动窗口移除最前面的元素,移除时判断该元素是否可以放入队列
mydeque.poll(nums[i-k]);
//滑动窗口加入最后面的元素
mydeque.add(nums[i]);
//记录对应的最大值
result[num++] = mydeque.peek();
}
return result;
}
}
1、ArrayDeque与Deque:
ArrayDeque是Deque接口的一个实现。Deque代表双端队列(double-ended queue),它是一种队列,允许在队列的两端进行插入和删除操作。Deque接口定义了双端队列的行为,而ArrayDeque是Deque接口的一个具体实现,它使用数组作为内部数据结构来实现双端队列的功能。
因此,ArrayDeque是Deque接口的一种实现方式,它提供了一种使用数组实现双端队列的方式。Deque接口还可以有其他的实现方式,比如LinkedList等。在Java中,我们可以使用Deque接口来表示双端队列的抽象概念,而具体使用哪种实现方式则可以根据具体的需求来选择。
2、大顶堆与小顶堆的适配应用场景分析:
可以在O(nlogk)的时间复杂度内找到前k个大的元素,而不需要对整个集合进行排序。
大顶堆和小顶堆是一种特殊的二叉堆数据结构,它们可以快速找到最大或最小的元素,并且在插入和删除元素时具有较好的性能表现。
在一个数据量庞大的集合中,如果需要找到前k个大的元素,可以使用大顶堆来实现。首先,可以维护一个大小为k的大顶堆,然后遍历集合中的元素,将每个元素与堆顶元素进行比较。如果当前元素大于堆顶元素,则将堆顶元素替换为当前元素,并进行堆调整,以保持堆的性质。最终,堆中的元素即为前k个大的元素。
类似地,如果需要找到前k个小的元素,可以使用小顶堆来实现。通过这种方法,可以在较短的时间内找到前k个大或小的元素,而不需要对整个集合进行排序,从而提高了算法的效率。
3、 关于map.getOrDefault(num, 0);
map.put(num, map.getOrDefault(num, 0) + 1);
这行代码是将
num
作为键,然后使用getOrDefault
方法来获取num
对应的值,如果num
不存在则返回默认值0。然后将获取到的值加1,最后将结果放入map
中,即将num
对应的值加1并更新到map
中。
getOrDefault
方法是Map接口中的一个方法,它的作用是获取指定键对应的值,如果键不存在,则返回一个默认值。这个方法接收两个参数,第一个参数是要获取值的键,第二个参数是默认值。如果指定的键存在,则返回与该键关联的值,如果键不存在,则返回提供的默认值。
4、关于以Map映射关系为元素的Set的应用:
Set> entries = map.entrySet();
上述代码是将map中所有的键值对(key-value)映射关系转化为一个Set集合,这个Set集合的元素类型是Map.Entry
。 Map.Entry表示一个映射项(键值对),其中Integer表示键的类型,第二个Integer表示值的类型。 "Entry" 是指 Map 接口中的一个内部接口,它表示一个键值对(key-value pair)。在这里,"Map.Entry
" 表示一个具有整数类型键和整数类型值的键值对。
entrySet()
是 Map 接口中的一个方法,它返回包含映射关系(键值对)的 Set 视图。这个 Set 中的每个元素都是一个 Map.Entry 对象,每个 Map.Entry 对象包含一个键和对应的值。通过调用entrySet()
方法,可以获取到 Map 中所有的键值对,并以 Set 的形式返回。
5、关于Java中的优先队列:
PriorityQueue> queue = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue());
这行代码的意思是创建了一个优先队列(PriorityQueue)对象,该队列中的元素是 Map.Entry
类型。同时,通过传入一个比较器(Comparator)来指定优先队列中元素的排序规则。在这里的比较器中,使用 o2.getValue() - o1.getValue() 来定义元素的排序规则,表示按照值的大小降序排列。 这行代码中传入了一个Lambda表达式作为比较器,Lambda表达式定义了如何对队列中的元素进行比较和排序。在这里,
(o1, o2) -> o2.getValue() - o1.getValue()
表示比较器的逻辑,其中o1
和o2
分别表示优先队列中的两个元素。这个比较器的逻辑是通过比较两个 Map.Entry对象的值来确定它们的顺序。具体来说, o2.getValue() - o1.getValue()
表示按照值的大小进行降序排列,也就是说,值较大的元素会被放在队列的前面。这样,当优先队列进行弹出操作时,会先弹出值较大的元素。而且,在Java中,优先队列(PriorityQueue)默认是小顶堆(Min Heap),也就是说,队列头部的元素是队列中最小的元素。因此,为了实现大顶堆(Max Heap),我们需要传入一个自定义的比较器来改变默认的排序规则。在这个例子中,通过传入
(o1, o2) -> o2.getValue() - o1.getValue()
这个Lambda表达式作为比较器,我们改变了默认的排序规则,使得值较大的元素会被放在队列的前面,从而实现了大顶堆的效果。这样,当优先队列进行弹出操作时,会先弹出值较大的元素。
6、关于Lambda表达式:
Lambda表达式是Java 8引入的一个重要特性,它提供了一种简洁的语法来表示匿名函数。Lambda表达式可以被视为一种匿名函数,它可以像普通方法一样传递给其他方法,也可以作为函数式接口的实例使用。
Lambda表达式一般语法:
(parameters) -> expression
或者
(parameters) -> { statements; }
其中,parameters表示参数列表,可以为空或非空;箭头 -> 将参数列表和Lambda表达式的主体分隔开;expression或{ statements; }表示Lambda表达式的主体,可以是一个表达式或一段代码块。
Lambda表达式的主要特点包括:
- 简洁性:Lambda表达式可以大大减少冗余代码,使代码更加简洁易读。
- 传递行为:Lambda表达式可以作为参数传递给方法,从而实现更灵活的行为传递。
- 并行处理:Lambda表达式可以与Stream API一起使用,实现并行处理数据的功能。
Lambda表达式在Java中被广泛应用于函数式接口、集合操作、并行处理等方面,极大地丰富了Java的编程语言特性。
7、 offer()函数是用于向队列中插入元素的方法,它在队列满了时(对于有界队列)会返回false,否则会插入元素并返回true。
即,将元素(key)及其出现次数(value)利用HashMap做一个存储,然后将每个元素及其出现次数作为映射关系存储在entrySet,最后利用选择器构建大顶堆,将在Set中存储的映射关系加入优先队列(大顶堆),然后控制弹出k个键值,即弹出了最大的k个元素的键值(key)。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//利用大顶堆实现
int[] result = new int[k];
HashMap map = new HashMap<>();
for(int num : nums){
map.put(num,map.getOrDefault(num, 0) + 1);
}
Set> entries = map.entrySet();
//通过选择器构建大顶堆
PriorityQueue> queue = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue());
for(Map.Entry entry : entries){
queue.offer(entry);
}
//取前k个放入result
for(int i = k - 1; i >= 0; i--){
result[i] = queue.poll().getKey();
}
return result;
}
}