1.知识点
滑动窗口的最大值:
通过双端队列维护一个单调递减队列,队头是最大元素
前K个高频元素:通过最小优先队列实现
2.刷题
239.滑动窗口最大值
LeetCode链接 239. 滑动窗口最大值 - 力扣(LeetCode)
题目描述
方法1:滑动窗口+单调递减队列
package daimasuixiangshuati.day12_zhanyuduilie;
import java.util.Deque;
import java.util.LinkedList;
/**
* @Author LeiGe
* @Date 2022/10/2
* @Description todo
*/
public class HuaDongChuangKouZuiDaZhi239_2 {
/**
* 方法1:滑动窗口+通过双端队列维护一个单调递减队列
* 0.单调递减队列,队列头为最大值
* 1.先形成第1个窗口
* 2.滑动窗口
* 2.1滑动窗口移除左侧元素 移除时,判断该元素是否是队列中的最大值,如果是,队头元素出队列
* 2.2滑动窗口加入右边的元素 加入时,判断队列中是否有比该元素小的值,如果有,删除这些比当前值小的元素
*
* @param nums
* @param k
* @return
*/
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) {
return nums;
}
//有多少个最大值
int len = nums.length - k + 1;
//存放结果元素
int[] res = new int[len];
//记录最大值
int num = 0;
//自定义队列
MyQueue myQueue = new MyQueue();
//先将前k个元素放入队列中
for (int i = 0; i < k; i++) {
myQueue.add(nums[i]);
}
//当前窗口内最大值
res[num++] = myQueue.peek();
//开始滑动窗口
for (int i = k; i < nums.length; i++) {
// 滑动窗口移除左侧元素
// 移除时,判断该元素是否是队列中的最大值,如果是,队头元素出队列
myQueue.poll(nums[i - k]);
// 滑动窗口加入右边的元素
// 加入时,判断队列中是否有比该元素小的值,如果有,删除这些比当前值小的元素
myQueue.add(nums[i]);
//记录对应的最大值
res[num++] = myQueue.peek();
}
return res;
}
/**
* 自定义数据结构,单调递减队列:队头永远是最大值
*/
private class MyQueue {
Deque<Integer> deque = new LinkedList<>();
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
public void poll(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
public void add(int val) {
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();
}
deque.addLast(val);
}
//队列队顶元素始终为最大值
int peek() {
return deque.peek();
}
}
}
时间复杂度:O(N)
空间复杂度:O(N)
方法2:滑动窗口+单调递减队列
package daimasuixiangshuati.day12_zhanyuduilie;
import java.util.LinkedList;
/**
* @Author LeiGe
* @Date 2023/10/29
* @Description todo
*/
public class HuaDongChuangKouZuiDaZhi239_3 {
/**
* 方法2:方法2:滑动窗口+通过双端队列维护一个单调递减队列
*
* @param nums
* @param k
* @return
*/
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length < 2) {
return nums;
}
// 双端队列
LinkedList<Integer> dequeue = new LinkedList<>();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 保证队列从大道小,如果前面的数小则需要弹出
while (!dequeue.isEmpty() && nums[dequeue.peekLast()] <= nums[i]) {
dequeue.pollLast();
}
// 添加当前值对应的数组下标
dequeue.addLast(i);
// 判断当前队列中队首元素是否有效
if (dequeue.peek() <= i - k) {
dequeue.pop();
}
// 当窗口长度为k时,保存当前窗口中最大值
if (i + 1 >= k) {
result[i + 1 - k] = nums[dequeue.peek()];
}
}
return result;
}
}
时间复杂度:O(N)
空间复杂度:O(N)
347.前K个高频元素
LeetCode链接 347. 前 K 个高频元素 - 力扣(LeetCode)
题目描述
方法1:最小优先队列(基于堆实现)
package daimasuixiangshuati.day12_zhanyuduilie;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
/**
* @Author LeiGe
* @Date 2022/10/2
* @Description todo
*/
public class QianKGeGaoPinYuanSu347_2 {
/**
* 方法1:最小优先队列
* 1.通过map统计出数组中每个元素出现的频率
* 2.构建1个最小优先队列,将map中的所有元素加入到最小优先队列中
* 2.1如果加入的过程中,队列大小大于K,则将顶端元素弹出(队列中维持k个元素,顶端最小)
* 3.队列中的k个元素就是最大的k个元素
*
* @param nums
* @param k
* @return
*/
public int[] topKFrequent(int[] nums, int k) {
int[] result = new int[k];
// 统计每个元素出现的频率
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
// 根据map的value值正序排,相当于一个小顶堆
PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
//将所有频率加入到小顶堆中,如果如果堆大小大于k,弹出(相当于弹出最小值)
for (Map.Entry<Integer, Integer> entry : entries) {
queue.offer(entry);
if (queue.size() > k) {
queue.poll();
}
}
//倒序数组输出结果
for (int i = k - 1; i >= 0; i--) {
result[i] = queue.poll().getKey();
}
return result;
}
}
时间复杂度:O(N*logN)
空间复杂度:O(N)
3.小结
栈相关的题目:
栈在系统中的应用
括号匹配问题
字符串相邻去重
逆波兰表达式
队列相关的题目:
滑动窗口最大值
前K个高频元素