前 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 是数组的大小。
解题思路
1、遍历一遍数组,将所有数字(key),和个数(value)统计装进HashMap
2、创建一个最小堆,当然你也可以直接使用PriorityQueue类
3、将HashMap中的数据根据元素的频率装进最小堆,堆的大小为k
4、这样频率前k高的元素就装进最小堆里
5、从堆里取出就可以了
代码实现
package solution;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Solution11 {
public static void main(String[] args) {
Solution11 solution11=new Solution11();
List<Integer> list=solution11.topKFrequent(new int[]{1,1,1,2,2,3},2);
System.out.println(list.toString());
}
/**
* 最小堆
* 基于Entry数组实现
*/
private class Max{
private class Entry{
private int k;
private int v;
public Entry(int k,int v){
this.k=k;
this.v=v;
}
}
//Entry数组
private Entry[] entries;
private int size;//元素的个数
/**
* 构造函数,创建一个大小为K的数组,保存k个元素
* @param k
*/
public Max(int k){
entries=new Entry[k];
size=0;
}
//父节点
private int parent(int index){
return (index-1)>>1;
}
//左子节点
private int leftChild(int index){
return (index<<1)+1;
}
//右子节点
private int rightChild(int index){
return (index<<1)+2;
}
//上浮动作,频率小的元素向上浮动
private void shiftUp(int index){
while (index>0){
int parent=parent(index);
if(entries[parent].v>entries[index].v){
swap(index,parent);
index=parent;
}else {
break;
}
}
}
//获得最小元素,也就是最顶端元素
public int getMin(){
return entries[0].k;
}
//删除最小元素
public int removeMin(){
int k=getMin();
//交换最顶端和最后一个元素的位置
swap(0,size-1);
//删除最后一个元素
entries[size-1]=null;
size--;
//再做下沉操作
shiftDown(0);
return k;
}
//下沉操作,将频率大的元素向下沉
private void shiftDown(int index){
while (leftChild(index)<size){
int k=leftChild(index);
if(k+1<size&&entries[k+1].v<entries[k].v){
k=rightChild(index);
}
if(entries[index].v>entries[k].v){
swap(index,k);
index=k;
}else {
break;
}
}
}
//交换
private void swap(int a,int b){
if(a==b){
return;
}
Entry temp=entries[a];
entries[a]=entries[b];
entries[b]=temp;
}
//添加元素
public void add(int k,int v){
Entry entry=new Entry(k,v);
//第一个元素直接添加
if(size==0){
entries[0]=entry;
size++;
return;
}
//堆里只添加k个元素
if(size<entries.length){
entries[size]=entry;
shiftUp(size);
size++;
}else{
//堆满了之后,添加元素与顶端元素进行比较,
// 如果顶端元素的频率小,则替换
if(entries[0].v<entry.v){
entries[0]=entry;
//再做下沉操作
shiftDown(0);
}
}
}
}
public List<Integer> topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map=new HashMap<>();
//将所有元素统计进map
for(int i=0;i<nums.length;i++){
if(map.get(nums[i])==null){
map.put(nums[i],1);
}else{
map.put(nums[i],map.get(nums[i])+1);
}
}
//创建堆
Max max=new Max(k);
//将频率前k高的元素添加进堆
for (Map.Entry<Integer,Integer> entry:map.entrySet()){
max.add(entry.getKey(),entry.getValue());
}
List<Integer> list=new ArrayList<>();
int[] va=new int[k];
//应该建个最大堆,最小堆取出来是反序
for (int i=k-1;i>=0;i--){
va[i]=max.removeMin();
}
for (int n:va){
list.add(n);
}
return list;
}
}