leetcode-347-前K個高頻元素(top k frequent elements)-java
題目及測試
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;
}
}