TopKFrequentElements [source code]
public class TopKFrequentElements {
static
/******************************************************************************/
public class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int i : nums) {
Integer count = map.getOrDefault(i, 0);
map.put(i, count + 1);
}
PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>(
new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> a, Map.Entry<Integer, Integer> b) {
return b.getValue() - a.getValue(); //descending order
}
}
);
pq.addAll(map.entrySet());
List<Integer> res = new ArrayList<>();
for (int i = 0; i < k; i++)
res.add(pq.poll().getKey());
return res;
}
}
/******************************************************************************/
public static void main(String[] args) {
TopKFrequentElements.Solution tester = new TopKFrequentElements.Solution();
}
}
note的第二条其实就是禁止你直接sort完了用分段算法来数. 注意这里规定的n
是整个的size. 然后你看tag里面的heap, 意思是可以用PriorityQueue, 用PriorityQueue的复杂度也是linearithmic, 但是它的N并不是array size这么大;
我就直接用PriorityQueue写法写写看吧. 这个题目之前好像见别人提过是有其他的解法的, 但是PriorityQueue我还没有写过, 正好这个题目写写看算了;
另外这一题既然最后会检查nlgn, 所以估计感觉这个n会相当大. 做counts的时候用hashing好像就不太靠谱了;
翻了一下以前代码, 想起来之前sort frequency的另外一种做法是什么了, 是hashing: 直接把counts作为index的第二个hashing; 这个做法感觉也不是很适合这里. 这里counts的上限并没有保障;
各种API写起来还是不太熟练,最后还是稍微瞄了一眼之前sort frequency题目的代码; 整体来说知道做法之后还是很简单的, 速度是31ms (55%).
这个是discussion上面一个用bucket sort做的解法:
public List<Integer> topKFrequent(int[] nums, int k) {
List<Integer>[] bucket = new List[nums.length + 1];
Map<Integer, Integer> frequencyMap = new HashMap<Integer, Integer>();
for (int n : nums) {
frequencyMap.put(n, frequencyMap.getOrDefault(n, 0) + 1);
}
for (int key : frequencyMap.keySet()) {
int frequency = frequencyMap.get(key);
if (bucket[frequency] == null) {
bucket[frequency] = new ArrayList<>();
}
bucket[frequency].add(key);
}
List<Integer> res = new ArrayList<>();
for (int pos = bucket.length - 1; pos >= 0 && res.size() < k; pos--) {
if (bucket[pos] != null) {
res.addAll(bucket[pos]);
}
}
return res;
}
implicit bound of frequency
我自己做的时候放弃bucket sort的一个原因是因为我认为这个bucket array的长度无法预知. 但是这里犯了一个错误, 之前做sort frequency的时候就已经翻过这个错误: frequency value本身是有一个隐含上限的, 也就是input array的size;
另外感觉他这里的做法还是有点奇怪; 他这个做的是一个完整的sort过程, 我们这里需要的好像实际上并不是sort? 只是要那K个entry的value就行了;
哦不对, 这里他bucket array之所以每一个entry用list是因为这里要考虑不同的entry用相同的count的情况; 这个是我自己犯蠢了; 这个算法最后的速度肯定是比PriorityQueue的做法要快的;
另外看这里, 我学到一个关于变量命名的关键. 我觉得以后变量命名, 尤其是数据结构类型的变量, 命名的时候至少要有两个词. 第一个词就是你自己随便起的名字, 比如freq, 第二个词最好能够反映这个变量的类型, 比如Map; 类似的, 这里的这个bucket array其实就可以直接命名成bArr
这样. 我现在的一贯做法是在第一个词的选择上纠结太久. 其实我感觉只要第二个类型词表达正确, 读得人一般是比较容易看懂的;
另外他这个代码有人认为有bug:
The idea is great but I found a bug which may not be caught by OJ.
for (int pos = bucket.length - 1; pos >= 0 && res.size() < k; pos--) {
if (bucket[pos] != null) {
res.addAll(bucket[pos]);
}
}
e.g.[1,1,1,1,1,1,2,2,2,3,3,3,4,4,4,5] k=2. In your code the res.addAll() would add 2,3,4 at same time. the result list will be size of 4 instead of 2, because you did not consider the tie cases. In such a case, you should pick 1 plus any one number among 2,3,4, but never all 3 numbers. My suggestion is as follows:
outerloop:
for (int i = n; i >= 0; i--) {
for (int j = 0; j < buckets[i].size(); j++) {
topK.add(buckets[i].get(j));
k--;
if (k == 0) {
break outerloop;
}
}
}
return topK;
算是一个小的疏忽; 他原来那个代码确实是会把所有tie的都算做只占用一个名次;
另外他这个代码还是值得学习一下;
但是这个bug实际上有一个更好的解决办法:
one caveat is that this will add all the results by default for same frequency numbers. For example for input [1,1,2,2,3,3,4], k=2
this will return [1,2,3] where if we were expecting only top 2 elements (k=2) in increasing order. We could avoid that by returning the subList:
return res.subList(0,k);
受到discussion里面的启发, 好像PriorityQueue最好的并不需要把所有的entry都加进去(这个办法最后的复杂度是nlgn, with n as the number of distinct numbers). 是否可以把PriorityQueue的size维持在K, 这样最后的复杂度就可以保证在KlgK? 但是想了一下, 这样一个操作就要求有一个removeLast的操作for PriorityQueue, 但是Google了一下, 好像这样一个操作并没有API. 除非自己手动做, 手动copy过去;
或许可以把PriorityQueue做成ascending的, 这样直接removeFirst就行了; 最后提取的时候也不麻烦! 因为你的PriorityQueue的size只有K, 直接全部扔到res里面就行了;
最后的代码是:
public class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int i : nums) {
Integer count = map.getOrDefault(i, 0);
map.put(i, count + 1);
}
PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>(
new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> a, Map.Entry<Integer, Integer> b) {
return a.getValue() - b.getValue(); //ascending order
}
}
);
for (Map.Entry<Integer, Integer> e : map.entrySet()) {
pq.offer(e);
if (pq.size() > k)
pq.poll();
}
List<Integer> res = new ArrayList<>();
for (Map.Entry<Integer, Integer> e : pq)
res.add(e.getKey());
return res;
}
}
然而这个代码最后的速度居然变慢了: 52(26). 完全不能理解; 按理说这个实际上是做到了Nlgk的.
这个是一个类似的用heap的做法:
// use maxHeap. Put entry into maxHeap so we can always poll a number with largest frequency
public class Solution {
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);
}
PriorityQueue<Map.Entry<Integer, Integer>> maxHeap =
new PriorityQueue<>((a,b)->(b.getValue()-a.getValue()));
for(Map.Entry<Integer,Integer> entry: map.entrySet()){
maxHeap.add(entry);
}
List<Integer> res = new ArrayList<>();
while(res.size()<k){
Map.Entry<Integer, Integer> entry = maxHeap.poll();
res.add(entry.getKey());
}
return res;
}
}
注意他这里comparator的写法, 直接用lambda作为参数, 省掉好多打字.
It's a shortcut for Comparator. You can spend some time to check Java 8 Lambda Expression. When you learn this, you do not want to write anonymous Comparator anymore.
这个人还提出另外一种做法:
// use treeMap. Use freqncy as the key so we can get all freqencies in order
public class Solution {
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);
}
TreeMap<Integer, List<Integer>> freqMap = new TreeMap<>();
for(int num : map.keySet()){
int freq = map.get(num);
if(!freqMap.containsKey(freq)){
freqMap.put(freq, new LinkedList<>());
}
freqMap.get(freq).add(num);
}
List<Integer> res = new ArrayList<>();
while(res.size()<k){
Map.Entry<Integer, List<Integer>> entry = freqMap.pollLastEntry();
res.addAll(entry.getValue());
}
return res;
}
}
这个TreeMap以前也是没有玩过. 可以看到实际上可以使用的API还是很丰富, 尤其是最后一个pass, 还有一个Poll的操作; 注意这里的这个last自动的就是默认的是ascending里面的last, 也就是是最大的一个;
pollFirst也是有API的, 具体可以参考: https://docs.oracle.com/javase/8/docs/api/java/util/TreeMap.html
这个方法和heap方法做到的功能性实际上是一样的, 就是sort by assoc value. 大概参考一下, 如果没有什么特别的, 感觉掌握PriorityQueue这个做法是比较主要的;
TreeMap provide all implementations in log(n) time: http://docs.oracle.com/javase/7/docs/api/java/util/TreeMap.html
所以就是一个跟heap非常类似的东西.
PriorityQueue还可以这样做的:
Queue<Map.Entry<Integer,Integer>> q = new PriorityQueue<>(Map.Entry.comparingByValue());
这个是默认的ascending, 适合我那个维护pq size as K的做法;
另外很多人争论, 这个PriorityQueue的做法到底应不应该算作是超时, 因为假如所有的element全都是distinct, 那么最后Map的entry的个数就是N = size of input array. 但是实际上我认为这个是不对的. 就算是这样的一个情况, 我们最后实际上的复杂度应该是lg1 + lg2 + ... + lgN
, 所以是没有达到NlgN的. 当然具体多少, 感觉就很难算;
一个用quick select的解法:
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
vector<int> res;
if (!nums.size()) return res;
unordered_map<int, int> cnt;
for (auto num : nums) cnt[num]++;
vector<pair<int, int>> num_with_cnt;
for (auto kv : cnt) {
num_with_cnt.push_back({kv.first, kv.second});
}
kselection(num_with_cnt, 0, num_with_cnt.size()-1, k);
for (int i = 0; i < k && i < num_with_cnt.size(); ++i) {
res.push_back(num_with_cnt[i].first);
}
return res;
}
void kselection(vector<pair<int, int>>& data, int start, int end, int k) {
if (start >= end) return;
auto pv = data[end];
int i = start;
int j = start;
while (i < end) {
if (data[i].second > pv.second) {
swap(data[i++], data[j++]);
} else {
++i;
}
}
swap(data[j], data[end]);
int num = j - start + 1;
if (num == k) return;
else if (num < k) {
kselection(data, j + 1, end, k - num);
} else {
kselection(data, start, j - 1, k);
}
}
};
学学可以, 但是这个做法好像最后是NlgN? 这个就他妈是自己写了一个sort啊. 等于这个题目的OJ对于sort的做法并没有判断超时;
基本没有更好的做法了;
Problem Description
Given a non-empty array of integers, return the k most frequent elements.
For example,
Given [1,1,1,2,2,3] and k = 2, return [1,2].
Note:
- You may assume k is always valid, 1 ≤ k < number of unique elements.
- Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
Difficulty:Medium
Total Accepted:67.5K
Total Submissions:140.9K
Contributor: LeetCode
Companies
pocket gems yelp
Related Topics
hash table heap
Similar Questions
Word Frequency Kth Largest Element in an Array Sort Characters By Frequency