算法训练营(day13)

239.滑动窗口最大值

题目链接:https://leetcode.cn/problems/sliding-window-maximum/description/

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值

解题思路

解题过程:

  1. 本题使用 双端队列 进行解答
  2. 主要思路是 定义队列存放数组下标,再定义新数组存放窗口内最大值的下标定义的值
  3. 要满足在滑动窗口内找最大值,需要在[i - k + 1, i] 中选到最大值,不符合就要弹出
  4. 要考虑如何使队列存放最大值的下标 – 始终保持队列首部为最大值的下标 ,不符合就弹出
  5. 新数组要在 i - k + 1 >= 0 后,说明滑动窗口内已经填充足够的值,再开始存储最大值

详细代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums == null || k < 1 || nums.length < k){
return null;
}
Deque<Integer> deque = new ArrayDeque<>(); //定义队列存放数组下标
int[] res = new int[nums.length - k + 1]; //存储最大值
int index = 0; //res 的下标

for(int i = 0; i < nums.length; i++){
while(!deque.isEmpty() && deque.peekFirst() < i - k + 1){ //要在[i - k + 1, i] 中选到最大值
deque.pollFirst(); //不符合就弹出
}
while(!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]){ //始终保持队列首部为最大值的下标
deque.pollLast(); //不符合就弹出
}
deque.offer(i);

if(i >= k - 1){ //当 i - k + 1 >= 0 时,说明滑动窗口已填充足够的值
res[index++] = nums[deque.peekFirst()]; //每滑动一步都将队列头节点放入结果
}
}
return res;
}
}

347.前K个高频元素

题目链接:https://leetcode.cn/problems/top-k-frequent-elements/description/

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

解题思路

解题过程:

  1. 本题使用 优先级队列 求解
  2. 首先是使用 map 统计 每个整数的 种类key 以及 出现次数value
  3. 优先级队列 中存储 map 这个 二元组
  4. 根据 优先级队列 的排序规则,依次弹出 k 个优先级队列的元素,即得到结果

详细代码

解法一:使用大根堆求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>(); //key为数组元素值,val为对应出现次数
for(int num : nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}


//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从大到小排,出现次数最多的在队头(相当于大顶堆)
PriorityQueue<int[]> queue = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);

for(Map.Entry<Integer, Integer> entry : map.entrySet()){ //大顶堆需要对所有元素进行排序
queue.add(new int[]{entry.getKey(), entry.getValue()});
}
int[] ans = new int[k];
for(int i = 0; i < k; i++){ //依次从队头弹出k个,就是出现频率前k高的元素
ans[i] = queue.poll()[0];
}
return ans;
}
}

解法二:使用小根堆求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for(int num : nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}

//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
PriorityQueue<int[]> queue = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);

for(Map.Entry<Integer, Integer> entry : map.entrySet()){ //小顶堆只需要维持k个元素有序
if(queue.size() < k){ //小顶堆元素个数小于k个时直接加
queue.add(new int[]{entry.getKey(), entry.getValue()});
}else{
if(entry.getValue() > queue.peek()[1]){ //当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
queue.poll(); //弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
queue.add(new int[]{entry.getKey(), entry.getValue()});
}
}
}
int[] ans = new int[k];
for(int i = 0; i < k; i++){
ans[i] = queue.poll()[0]; //依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
}
return ans;
}
}