优先队列常见算法题
原创
©著作权归作者所有:来自51CTO博客作者wx5add7776993de的原创作品,请联系作者获取转载授权,否则将追究法律责任
数组中的第K个最大元素
数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素,请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
使用优先队列和栈解决
- 建堆
- 前k个元素出队
- 栈保存出队元素
- 栈顶即为第k大元素
/**
* 基于优先队列实现
* @param nums
* @param k
* @return
*/
public int findKthLargest(int[] nums, int k) {
//自定义排序规则
Comparator<Integer> comparator = new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
//使用优先队列建堆
Queue<Integer> queue = new PriorityQueue<>(comparator);
for(int i : nums){
queue.offer(i);
}
//获取前k个最大元素
Stack<Integer> stack = new Stack<>();
for(int i = 1;i <= k;i++){
stack.push(queue.poll());
}
//栈顶即第k大元素
return stack.pop();
}
也可以基于数组实现
/**
* 基于数组实现
* @param nums
* @param k
* @return
*/
public int findKthLargest1(int[] nums, int k) {
//排序
Arrays.sort(nums);
//因为排序是从小到大的,所以需要获取倒数的,即 len - k位置
return nums[nums.length - k];
}
设计一个找到数据流中第K大元素的类
设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。你的 KthLargest 类需要一个同时接收整数 k 和整数数组nums 的构造器,它包含数据流中的初始元素。每次调用 KthLargest.add,返回当前数据流中第K大的元素。
示例:
int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5
kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8
kthLargest.add(4); // returns 8
这个与前面不同的是,要可以这不断有元素入队和出队的过程中,能正确返回正确的结果
- 维护一个长度为k的堆,此堆就是维护了前k大的元素集合
- add()是构造堆,关于具体元素的上滤和下滤的位置调整交由PriorityQueue
- 入堆:
- 如果堆顶元素 < 插入元素x,堆顶元素先出队,后让x入队
/**
* 【基于堆】
* 使用优先队列最小堆,维护一个 k 大小的最小堆
*/
int k;
Queue<Integer> queue;
public KthLargest(int k, int[] nums) {
this.k = k;
//构造堆
queue = new PriorityQueue<>(k);
for(int i : nums){
add(i);
}
}
/**
* 入队并且获取第k大的数
* @param val
* @return
*/
public int add(int val) {
if(queue.size() < k){
queue.offer(val);
}else if(queue.peek() < val){
queue.poll();
queue.offer(val);
}
return queue.peek();
}
前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
这道题相比之前的两道题目多了频次的,我们可以用之前学过的哈希表去保存某数字和其出现的频次,然后频次的比较作为比较规则
public List<Integer> topKFrequent(int[] nums, int k) {
//使用散列表来保存数组每一个数的出现频率
Map<Integer,Integer> map = new HashMap<>();
for (int n: nums) {
map.put(n, map.getOrDefault(n, 0) + 1);
}
//根据频次排序作为比较规则
Queue<Integer> queue = new PriorityQueue<>((x,y)->map.get(x) - map.get(y));
//所有键入队,当队长大于k时,原来入队的元素出队
for(int i : map.keySet()){
queue.offer(i);
if(queue.size() > k){
queue.poll();
}
}
//保存结果
ArrayList<Integer> list = new ArrayList<>(k);
while(!queue.isEmpty()){
list.add(queue.poll());
}
return list;
}
根据字符出现频率排序
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
示例 1:
输入:
"tree"
输出:
"eert"
解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入:
"cccaaa"
输出:
"cccaaa"
解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入:
"Aabb"
输出:
"bbAa"
解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
实现
/**
* 基于优先队列解决
* @param s
* @return
*/
public String frequencySort(String s) {
char[] chars = s.toCharArray();
StringBuilder sb = new StringBuilder();
Map<Character, Integer> map = new HashMap<>();
//map统计频次
for (char c : chars) {
map.put(c, map.getOrDefault(c, 0) + 1);
System.out.println(map.get(c));
}
PriorityQueue<Map.Entry<Character, Integer>> queue = new PriorityQueue<>((v1, v2) -> v2.getValue() - v1.getValue());//大顶堆
//建堆
queue.addAll(map.entrySet());
int len = queue.size();
for (int i = 0; i < len; i++) {
char key = queue.peek().getKey(); //获取堆顶元素的键
int value = queue.poll().getValue();//获取并弹出堆顶元素的值
//组合字符
while (value-- > 0) {
sb.append(key);
}
}
return sb.toString();
}