题目及测试
package pid374;
/* 前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 是数组的大小。
*/
import java.util.List;
public class main {
public static void main(String[] args) {
int[][] testTable = {{1,1,1,2,2,3},{1}};
int[] testTable2={2,1};
for (int i=0;i<testTable.length;i++) {
test(testTable[i],testTable2[i]);
}
}
private static void test(int[] ito,int ito2) {
List<Integer> rtn;
Solution solution=new Solution();
long begin = System.currentTimeMillis();
for (int i = 0; i < ito.length; i++) {
System.out.print(ito[i]+" ");
}
System.out.println();
//开始时打印数组
System.out.println("ito2="+ito2);
rtn = solution.topKFrequent(ito,ito2);//执行程序
long end = System.currentTimeMillis();
System.out.println(ito + ": rtn=" );
System.out.println( " rtn=" );
for (int i = 0; i < rtn.size(); i++) {
System.out.print(rtn.get(i)+" ");
}//打印结果几数组
System.out.println();
System.out.println("耗时:" + (end - begin) + "ms");
System.out.println("-------------------");
}
}
解法1(成功,22ms,很快)
其实该提就是对map的value进行排序,方法
List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>>(map.entrySet());
Collections.sort(list, new Comparator<Entry<Integer, Integer>>() {
@Override
public int compare(Entry<Integer, Integer> o1,
Entry<Integer, Integer> o2) {
if(o2.getValue().compareTo(o1.getValue())>0){
return 1;
}else if(o2.getValue().compareTo(o1.getValue())<0){
return -1;
} else {
return 0;
}
}
先建立map,key为int[i],value为该值得次数,然后将map化为set,化为list,用collections。Sort对list排序,然后取出前k个的key即可
package pid374;
import java.lang.reflect.Array;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map=new HashMap<>();
List<Integer> result=new ArrayList<>();
int length=nums.length;
for(int i=0;i<length;i++){
int now=nums[i];
if(map.containsKey(now)){
map.put(now, map.get(now)+1);
}
else{
map.put(now,1);
}
}
List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>>(map.entrySet());
Collections.sort(list, new Comparator<Entry<Integer, Integer>>() {
@Override
public int compare(Entry<Integer, Integer> o1,
Entry<Integer, Integer> o2) {
if(o2.getValue().compareTo(o1.getValue())>0){
return 1;
}else if(o2.getValue().compareTo(o1.getValue())<0){
return -1;
} else {
return 0;
}
}
});
for(int i=0;i<k;i++){
result.add(list.get(i).getKey());
}
return result;
}
}
解法2(别人的)
该方法基于每个数的次数不重复
step1.显然,为了找出数组中出现频次最多的前k个元素,首先,我们需要分别统计出数组中各个元素出现的频次,很容易想到哈希表,Java中提供了HashMap类,它实现了Map接口,HashMap是一个泛型类(HashMap<key,value>),可以用来存储键/值对,为了统计数组个元素的频次,我们可以把元素数值作为“键”,对应元素出现的次数作为“值”,如此,我们只需要对数组进行一次遍历就可以得到一张包含不同数组元素和对应出现频次的“映射表”。
step2.由于我们关心的是出现频次最多的前k个元素,因此,得到频次统计“映射表”之后,我们需要根据频次对映射表中的键/值对进行排序。
step3. 映射表中键(数据元素)和值(该数据元素出现的频次)是一一对应的,我们在按值进行排序的同时需要记录其对应的元素,鉴于此,我们可以采用“桶排序”的思想。由于我们是按数据元素出现的频次进行排序的,那么“桶”的数量范围是可以确定的——桶的数量小于等于给定数组元素的个数。编号为i的桶用于存放数组中出现频次为i的元素——即编号为i的桶存放“映射表”中“值”等于i的“键”。
step4. 排序完成后,编号大的桶中元素出现的频次高,因此,我们“逆序”(先取桶编号大的桶的元素)获取桶中数据,直到获取数据的个数等于k,我们将当前桶的元素取尽(同一个桶中元素出现的频次相等),然后停止取数据,完成!
public class Solution {
public List<Integer> topKFrequent(int[] nums, int k)
{
//step1—用哈希表统计数组中各元素出现的频次,表中“键”为元素数值,“值”为对应元素出现的频次
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int num:nums)//遍历数组
{
if(map.get(num)==null)//如果“键”为num的数据首次出现,则“值”设为1
map.put(num, 1);
else
map.put(num, map.get(num)+1);//重复出现,则累计频次
}
//step2—桶排序
List<Integer>[] bucket=new List[nums.length+1];//定义足够数量的桶
for(int key:map.keySet())//按“键”遍历
{
int count=map.get(key);//获取数值为key的元素出现的频次
//把出现频次相同的元素“扔”到序号等于频次的桶中
if(bucket[count]==null)
bucket[count]=new ArrayList<Integer>();
bucket[count].add(key);
}
//step3—“逆序”取数据
List<Integer> result=new ArrayList<Integer>();
for(int i=nums.length;i>0;i--)//注意i的起始值,当数组只有一个数据时
{
if(bucket[i]!=null&&result.size()<k)
result.addAll(bucket[i]);
}
return result;
}
}
解法3(别人的)
最小堆
进一步,为了满足时间复杂度要求,需要对解法一的排序过程进行改进。因为最终需要返回前 k
个频率最大的元素,可以想到借助堆这种数据结构。通过维护一个元素数目为 k 的最小堆,每次都将新的元素与堆顶端的元素(堆中频率最小的元素)进行比较,如果新的元素的频率比堆顶端的元素大,则弹出堆顶端的元素,将新的元素添加进堆中。最终,堆中的 k 个元素即为前 k 个高频元素。
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
// 统计元素的频率
Map<Integer, Integer> map = new HashMap<>(16);
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
// 遍历map,用最小堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
// PriorityQueue<Integer> pq = new PriorityQueue<>(
// (a, b) -> map.get(a) - map.get(b)
// );
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
// 取出最小堆中的元素
List<Integer> ret = new ArrayList<>();
while (!pq.isEmpty()) {
ret.add(pq.remove());
}
return ret;
}
}