Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LeetCode 347. Top K Frequent Elements #46

Open
Woodyiiiiiii opened this issue May 8, 2020 · 0 comments
Open

LeetCode 347. Top K Frequent Elements #46

Woodyiiiiiii opened this issue May 8, 2020 · 0 comments

Comments

@Woodyiiiiiii
Copy link
Owner

Woodyiiiiiii commented May 8, 2020

Given a non-empty array of integers, return the k most frequent elements.

Example 1:

Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]

Example 2:

Input: nums = [1], k = 1
Output: [1]

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.
  • It's guaranteed that the answer is unique, in other words the set of the top k frequent elements is * unique.
  • You can return the answer in any order.

题目要求的是返回前K个经常出现的数,是求前K个最大的数的升级版(书上的例题)。
这类问题可以创建一个最小(大)堆,达到O(nlogn)的时间复杂度。我们可以利用HashMap保存数组中元素和它出现次数的映射,作为比较关系,建立堆。

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        int n = nums.length;
        int[] res = new int[k];
        int i = 0, j = 0;
        HashMap<Integer, Integer> map = new HashMap();
        // (nums, times) put into map
        for (i = 0; i < n; ++i) {
            if (!map.containsKey(nums[i]))
                map.put(nums[i], 1);
            else {
                int t = map.get(nums[i]);
                map.replace(nums[i], t + 1);
            }
        }
        // initalize array res 
        HashSet<Integer> kNums = new HashSet();
        for (i = 0; i < n && j < k; ++i) {
            if (!kNums.contains(nums[i])) {
                kNums.add(nums[i]);
                // res[j++] = nums[i];
                heapInsert(res, nums[i], map, j);
                ++j;
            }
        }
        // min heap
        for (; i < n; ++i) {
            if (!kNums.contains(nums[i]) && map.get(nums[i]) > map.get(res[0])) {
                kNums.add(nums[i]);
                res[0] = nums[i];
                heapify(res, 0, k, map);
            }
        }
        return res;
    }
    public void heapInsert(int[] heap, int value, 
            HashMap<Integer, Integer> map, int index) {
        heap[index] = value;
        while (index != 0) {
            int parent = (index - 1) / 2;
            if (map.get(heap[parent]) > map.get(heap[index])) {
                int t = heap[parent];
                heap[parent] = heap[index];
                heap[index] = t;
                index = parent;
            }
            else break;
        }
    }
    public void heapify(int[] heap, int index, int k, HashMap<Integer, Integer> map) {
        int left = index * 2 + 1, right = index * 2 + 2;
        int smallest = index;
        while (left < k) {
            if (map.get(heap[left]) < map.get(heap[index]))
                smallest = left;
            if (right < k && map.get(heap[smallest]) > map.get(heap[right]))
                smallest = right;
            if (smallest != index) {
                int t = heap[smallest];
                heap[smallest] = heap[index];
                heap[index] = t;
            }else {
                break;
            }
            index = smallest;
            left = index * 2 + 1;
            right = index * 2 + 2;
        }
    }
}

当然还可以使用Java集合类中的优先队列PriorityQueue,实际上就是最大(小)堆。
我卡在了如何定义构造器创建合适的优先队列,然后看了如下大佬的做法:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //check input
        if(nums == null || nums.length == 0 || k < 1) return new int[1];
       
   	    //HashMap to count frequency of the elements
   	    //Key as number and values as frequency or counts
        Map<Integer, Integer> map = new HashMap<>();
       
   	    //count number of frequencies for given array
        for(int num: nums) {
            map.put(num, map.getOrDefault(num,0) + 1);
        }

   	    //priority Queue(Max heap) to with custome comparator (Descending order)
        Queue <Integer> queue = new PriorityQueue<>((e1,e2) -> map.get(e2) - map.get(e1));
       
   	    //Add all elements(which will sort via their frequency or counts)
        queue.addAll(map.keySet());
       
        int[] result = new int[k];
        int i = 0;
        while(!queue.isEmpty()) {
            result[i++] = queue.poll();
            if(i==k) break;
        }
        return result;
    }
}

这段代码里有很多我没见过的并且很有意思的写法:

  1. map.getOrDefault(Object key, V defaultValue)方法官方讲解是"Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key.",即如果Map中有Key,则返回配对的Value,否则返回参数defalutValue,而加上map.put()方法,意味着如果Map中有Key,则取出Value然后加一再更新(put方法有更新的作用:"If the map previously contained a mapping for the key, the old value is replaced"),如果没有Key,则将(Key, 1)加入Map中。
  2. new PriorityQueue<>((e1,e2) -> map.get(e2) - map.get(e1)),这里设计如何定义Map的键值对的构造器,有三种写法,可以看参考资料3。这里不同的是,Queue的类型是Integer类的,加入了Map来实现compare方法,并不是常规的Map.Entry<>。
  3. queue.addAll(map.keySet());参数是Collection类,可以一次性加入Collection类或实现了Collection类的子类。

2020/09/09:我又来重写了一遍:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        if(nums == null || nums.length == 0 || k < 1) {
            return new int[1];
        }

        HashMap<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        PriorityQueue<Map.Entry<Integer, Integer>> queue =
                new PriorityQueue<>(new Comparator<Map.Entry<Integer, Integer>>() {
                    @Override
                    public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
                        if (o1.getValue().equals(o2.getValue())) {
                            return o1.getKey() - o2.getKey();
                        }
                        return o2.getValue() - o1.getValue();
                    }
                });

        queue.addAll(map.entrySet());

        int[] res = new int[k];
        int i = 0;
        while ((k--) != 0 && !queue.isEmpty()) {
            res[i++] = queue.poll().getKey();
        }
        
        return res;
    }
}

既然可以用优先队列,那也可以使用TreeMap,TreeMap的按键排序简单,但按值排序需要将其转化为list,再使用Collection.sort进行排序:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //check input
        if(nums == null || nums.length == 0 || k < 1) return new int[1];
       
   	    //HashMap to count frequency of the elements
   	    //Key as number and values as frequency or counts
        Map<Integer, Integer> map = new TreeMap<Integer, Integer>();
        Map<Integer, Integer> sortedMap = new LinkedHashMap<Integer, Integer>();
       
   	    //count number of frequencies for given array
        for(int num: nums) {
            map.put(num, map.getOrDefault(num,0) + 1);
        }
        
        // map converted to list and sort
        List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>> (map.entrySet());
		Collections.sort(list,new Comparator<Map.Entry<Integer,Integer>>() {
            @Override
            public int compare(Map.Entry<Integer, Integer> o1, 
                               Map.Entry<Integer, Integer> o2) {
                // return o1.getValue().compareTo(o2.getValue());
                return o2.getValue() - (o1.getValue());
            }
        });
       
        // 使用LinkedHashMap确保真正排好序了
        Iterator<Map.Entry<Integer, Integer>> iter = list.iterator();
        Map.Entry<Integer, Integer> tmpEntry = null;
        while (iter.hasNext()) {
            tmpEntry = iter.next();
            sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
        }
        
        int[] result = new int[k];
        int i = 0;
        for (Map.Entry<Integer, Integer> entry : sortedMap.entrySet()) {
            result[i++] = entry.getKey();
            if (i == k) break;
        }
        return result;
    }
}

这段代码有两个Map,TreeMap和LinkedHashMap,因为Collections.sort排好序后TreeMap顺序仍然不变,所以需要将排好序的list中元素放进LinkedHashMap中确保排好序。LinkedHashMap的优点在于,它不同于HashMap根据HashCode值存储数据,它保持了插入顺序,但同时速度慢,相应知识可以查看参考资料5。这样平均运行时间增大了很多,所以还不如用PriorityQueue。

还有一种方法是利用桶排序的方法,详细做法可以看下方参考资料6。


参考资料:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant