239. 滑动窗口最大值
-
给你一个整数数组
nums
,有一个大小为k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k
个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
思考
- 不愧为hard,思想理解,代码还是很难写出来。。
- 滑动窗口的每段的最大值,容易想到“最大堆、最小堆”来解决,但是解决不了窗口滑动后移除元素的问题
- 单调队列:递增、递减顺序的队列,所有元素按某一顺序存储
- 此题中队列中的元素:
- 要么是最大值
- 要么是递增顺序的一些列值,最大值在队首,次大值在队列第二个位置。。。
- 队列中存放元素值还是存放下标???
- 滑动窗口,每次需要将最前面的元素移除,若存放元素值,则无法做到这一点
复杂度
- 时间O(n)
- 空间O(n)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] res = new int[nums.length - k + 1];
int idx = 0 ;
LinkedList<Integer> deque = new LinkedList<>();
for (int i = 0; i < nums.length; i++) {
// 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peek() < i - k + 1){
deque.poll();
}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
if(i >= k - 1){
res[idx++] = nums[deque.peek()];
}
}
return res ;
}
}
第二次编码
- 维护一个最大队列(队列首部是最大值)
- 队列尾部和当前值比较,小于当前值得尾部的值都要弹出,保证队列顺序
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] res = new int[nums.length-k+1];
int pos = 0 ;
Deque<Integer> que = new LinkedList<>();
for (int i = 0; i < nums.length; i++) {
//队列中下标范围,i-k+1 ,i ,保证队列长度不会超过k,队列升序,可以把对首弹出
while(!que.isEmpty() && que.peek() < i-k+1){
que.poll();
}
// 保证对首永远是所有元素中的最大值
while(!que.isEmpty() && nums[que.peekLast()] < nums[i]){
que.pollLast();
}
//以上两步执行后,会将小于当前值得元素下标移除队列,保证队列得升序
que.offer(i);
if(i>= k-1){
res[pos++] = nums[que.peek()];
}
}
return res;
}
}
第三次编码
public int[] maxSlidingWindow(int[] nums, int k) {
int[] res = new int[nums.length - k +1];
int pos = 0 ;
//存储数组中元素的下标
Deque<Integer> que = new LinkedList<>();
for (int i = 0; i < nums.length; i++) {
//控制下标范围
while(!que.isEmpty() && que.peek() < i-k+1){
que.poll();
}
//控制要入队的数据比对位的要大,移除队尾较小的值
while(!que.isEmpty() && nums[que.peekLast()] < nums[i]){
que.pollLast();
}
que.offer(i);
if(i>=k-1){
//够k位,即队列元素个数大于等于k
res[pos++] = nums[que.peek()];
}
}
return res ;
}
347.前 K 个高频元素
- 给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
思考
- 前N个元素或者高频出现的元素容易想到优先队列,也成为了最大堆、最小堆
- 用哪个?
- 此题要求返回频次高的k个元素,换句话说,要把频次低的元素从队列中移除,而移除元素只能从堆顶移除,故需要使用最小堆
- 最小堆的顺序使用哪个?简单的存放元素值吗?
- 每次移除的元素一定是频次低的元素,所以需要根据存放元素出现的频次,使其自平衡
- 队列中存放的元素即为频率最高的元素集合
复杂度
- 时间 O(nlongn)
- 空间 O(n)
import java.util.HashMap;
import java.util.PriorityQueue;
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
PriorityQueue<Integer> que = new PriorityQueue<Integer>((a,b)->map.get(a)-map.get(b));
for (int i = 0; i < nums.length; i++) {
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
for (int v:map.keySet()) {
if(que.size()<k){
que.offer(v);
}else{
if(map.get(que.peek()) < map.get(v)){
que.poll();
que.offer(v);
}
}
}
int[] res = new int[k];
int idx = 0 ;
while (!que.isEmpty()) {
res[idx++] = que.poll();
}
return res ;
}
}
总结
-
栈和队列的题,重点在于构造队列或栈,大多数题需要把每次计算的结果重新放入容器内,参与下次计算
-
单调栈、单调队列:元素按某一顺序存放
-
最大堆、最小堆:分析每次需要移除的元素,确定用哪个,在构造函数中指定顺序