- 队列的应用:单调队列
当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。
由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。
为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」。
public int[] maxSlidingWindow(int[] nums, int k) {
// 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
//输出:[3,3,5,5,6,7]
Deque<Integer> deque = new LinkedList<>();
deque.push(0);
for (int i = 1; i < k; i++) {
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
}
int len = nums.length;
int[] res = new int[len-k+1];
res[0] = nums[deque.peekFirst()];
int num = 1;
for (int i = k; i < len; i++) {
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (deque.peekFirst() < i-k+1) {
deque.pollFirst();
}
res[num++] = nums[deque.peekFirst()];
}
return res;
}
- 借助 哈希表 来建立数字和其出现次数的映射,遍历一遍数组统计元素的频率
- 维护一个元素数目为
k
的最小堆 - 每次都将新的元素与堆顶元素(堆中频率最小的元素)进行比较
- 如果新的元素的频率比堆顶端的元素大,则弹出堆顶端的元素,将新的元素添加进堆中
- 最终,堆中的
k
个元素即为前k
个高频元素
public int[] topKFrequent(int[] nums, int k) {
// 1. 统计频率
HashMap<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
// 2.构造优先级队列
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1]-o2[1];
}
});
// Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
int key = entry.getKey();
int value = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < value) {
queue.poll();
queue.offer(new int[]{key,value});
}
}
else {
queue.offer(new int[]{key,value});
}
}
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = queue.peek()[0];
queue.poll();
}
return res;
}