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] 提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
综合代码:
class MyQueue {
// 使用双端队列来维护滑动窗口中的元素
Deque<Integer> deque = new LinkedList<>();
// 弹出元素的方法
// 比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
// 同时判断队列当前是否为空
void poll(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll(); // 队列不为空且队头元素等于val时,弹出队头元素
}
}
// 添加元素的方法
// 添加元素时,如果要添加的元素大于队列尾部的元素,就将尾部元素弹出
// 目的是保持队列中的元素单调递减
void add(int val) {
// 如果队列不为空且队尾元素小于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) {
// 如果数组长度为1,直接返回该数组
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;
}
}
347.前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
- 输入: nums = [1,1,1,2,2,3], k = 2
- 输出: [1,2]
示例 2:
- 输入: nums = [1], k = 1
- 输出: [1]
提示:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 $O(n \log n)$ , n 是数组的大小。
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
- 你可以按任意顺序返回答案。
堆是一颗完全二叉树,树中每个节点的值都不大于或小于其左右孩子的值。
这里使用小顶堆比较合适,把小顶堆里的出现次数少的值弹出后,堆里留下出现次数多的值,就是我们需要求的
综合代码:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 边界检查,如果 k 大于 nums 的长度,返回一个空数组
if (k > nums.length) {
return new int[0];
}
// 创建一个HashMap,key为数组元素值,value为对应的出现次数
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
// 优先队列 (小顶堆),用于存储前 k 个频繁元素
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(pair -> pair[1]));
// 遍历map中的每一个元素及其出现次数
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
// 如果小顶堆的元素个数小于 k 个时,直接将当前元素加入小顶堆
if (pq.size() < k) {
pq.add(new int[]{entry.getKey(), entry.getValue()});
} else {
// 如果当前元素的出现次数大于小顶堆的根节点(即堆中出现次数最少的那个)
if (entry.getValue() > pq.peek()[1]) {
// 弹出队头(小顶堆的根节点),即删除堆中出现次数最少的那个元素
pq.poll();
// 将当前元素加入小顶堆
pq.add(new int[]{entry.getKey(), entry.getValue()});
}
}
}
// 创建结果数组,用于存储前 k 个出现频率最高的元素
int[] ans = new int[k];
// 依次弹出小顶堆中的元素,将元素值存储在结果数组中
// 先弹出的是堆的根,出现次数少,后面弹出的出现次数多
for (int i = k - 1; i >= 0; i--) {
ans[i] = pq.poll()[0];
}
// 返回结果数组
return ans;
}
}