目录
1.最小K个数:OJ链接
题目描述:
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4 输出: [1,2,3,4]提示:
0 <= len(arr) <= 100000
0 <= k <= min(100000, len(arr))
思路:在TopK问题中有一个很重要的规则:取大构建小堆,取小构建大堆。
在这个题中要取最小K个数,所以要建大堆,但是JDK的优先级队列是最小堆,因此我们要给优先级队列传入一个比较器将最小堆转为最大堆。
构建一个大小为k的最大堆,遍历数组,扫描元素,若队列中的元素小于K时,直接入队,当队列中元素大于K时,开始进行一个“打擂”的过程,若元素小于当前队首元素时,就将队首元素出队,并将该元素入队,当遍历结束后,队列中的元素就是所求最小的K个数。通过打擂的过程,不断将堆的最大值更新,堆的最大值会越来越小。
public class Num1714 {
public int[] smallestK(int[] arr, int k) {
int[] ret=new int[k];
//边界条件注意
if (arr.length==0 || k==0){
return ret;
}
//构建一个最大堆
//JDK的优先级队列是最小堆,如何把最小堆变为最大堆?
//就要用到我们比较器
//元素越小优先级越高,变为最大堆,元素越大优先级越高
Queue<Integer> queue=new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
//遍历原数组,扫描元素,将元素加入最大堆
for(int value:arr){
if(queue.size()<k){
queue.offer(value);
}else {
if(value<queue.peek()){
queue.poll();
queue.offer(value);
}
}
}
//当扫描完整个原数组时,整个队列的元素就是所求的最小元素
for (int i = 0; i < k; i++) {
ret[i]=queue.poll();
}
return ret;
}
}
2.前K个高频元素:OJ链接
题目描述:
给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]示例 2:
输入: nums = [1], k = 1 输出: [1]提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
思路:TopK问题规则:取大建小堆。我们可以用一个内部类,类中的属性包含数组中出现的不同元素和出现的频次,然后将元素以及出现的频次存入Map中,再扫描Map将前K个出现频率最高的元素存入优先级队列中,给优先级队列中传入一个比较器,按照元素出现频率高低来定义优先级。
最后遍历Map,建立大小为K的堆,若队列的大小小于K,直接将数对入队列,若队列长度大于K,就进行一个“打擂”的过程,若Map中的元素大于当前队首元素,将队首元素出队,并且将当前Map中的数对入队。遍历结束后,队列中就是所求的前K个高频元素。
public class Num347 {
private class Freq{
//不同的元素
int key;
//出现的频率
int freq;
public Freq(int key, int freq) {
this.key = key;
this.freq = freq;
}
}
public int[] topKFrequent(int[] nums, int k) {
//1.扫描原数组,将每个元素及出现的次数存入Map中
Map<Integer,Integer> map=new HashMap<>();
for (int value:nums){
map.put(value,map.getOrDefault(value,0)+1);
}
//2.扫描Map集合,将前k个出现频率最高的存入优先级队列(最小堆)
//向优先级队列中传入一个比较器,比较的是两个Freq对象的value值谁大谁小 o1-o2
PriorityQueue<Freq> queue=new PriorityQueue<>(new Comparator<Freq>() {
@Override
public int compare(Freq o1, Freq o2) {
return o1.freq-o2.freq;
}
});
//Map集合遍历
//每个entry存储了出现的元素key,以及出现的次数value
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(queue.size()<k){
queue.offer(new Freq(entry.getKey(), entry.getValue()));
}else {
//当前entry.value>堆顶元素才出队
Freq peekFreq=queue.peek();
if(entry.getValue()> peekFreq.freq){
//出队,更新为频次更大的元素
queue.poll();
queue.offer(new Freq(entry.getKey(),entry.getValue()));
}
}
}
int[] ret=new int[k];
for (int i = 0; i < k; i++) {
ret[i]=queue.poll().key;
}
return ret;
}
}
3.查找和最小的K对数字:OJ链接
题目描述:
给定两个以升序排列的整数数组 nums1 和 nums2 , 以及一个整数 k 。
定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。
请找到和最小的 k 个数对 (u1,v1), (u2,v2) ... (uk,vk) 。
思路:首先我们用一个内部类,其中的属性有u(第一个数组元素)、v(第二个数组元素)。其次给优先级队列中传入比较器,利用数对和的大小来决定优先级。最后进行双层循环,若队列中的元素小于K,直接将当前两层循环组成的数对入队列,若队列元素大于K,当前的数对和小于队首数对和,则将队首数对出队,将当前的元素组成的数对入队。最终循环结束 ,队列中剩余的元素就是和最小的数对,将它先存入一个个小数组中,再将小数组存入二维数组,返回二维数组就得到了和最小的K对数字。
这个题中需要注意的时K和两个数组长度的关系,当k<=nums1.length || k<=nums2.length时,在双层循环取两个数组的元素时,只需要走K步,不用走整个nums.length步(因为要找和最小的K对数字,且数组升序,所以不需要走到数组中K之后的元素,一定比K之前的元素组成的数对的和大!!!)。
public class Num373 {
private class Freq{
//第一个数组元素
int u;
//第二个数组元素
int v;
public Freq(int u, int v) {
this.u = u;
this.v = v;
}
}
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
Queue<Freq> queue=new PriorityQueue<>(new Comparator<Freq>() {
@Override
public int compare(Freq o1, Freq o2) {
return (o2.u+o2.v)-(o1.u+o1.v);
}
});
//最外层取u值
for (int i = 0; i < Math.min(k, nums1.length); i++) {
//最内层取v值
for (int j = 0; j < Math.min(k, nums2.length); j++) {
if(queue.size()<k){
queue.offer(new Freq(nums1[i],nums2[j]));
}else {
Freq freq= queue.peek();
if(nums1[i] + nums2[j] < freq.u+ freq.v){
queue.poll();
queue.offer(new Freq(nums1[i],nums2[j] ));
}
}
}
}
//队列中存储了前k个最小freq对象
List<List<Integer>> ret=new ArrayList<>();
//边界条件 k>queue.size()
for (int i = 0; i < k && !queue.isEmpty(); i++) {
List<Integer> tmp=new ArrayList<>();
Freq freq= queue.poll();
tmp.add(freq.u);
tmp.add(freq.v);
ret.add(tmp);
}
return ret;
}
}