[LC] 347. Top K Frequent Elements

 

这题做法其实不止一种。最直观的做法就是用heap,在java里就是PriorityQueue。用一个大小为k的最小堆来做,对每个数字出现的次数做统计,保留出现次数最大的k个即可。当然你放在PriorityQueue里面的不能是一个简单的Integer,因为你不止要知道次数的排序,你还得知道是什么数字对应的那个次数。所以你就需要override默认的comparator才行,你PriorityQueue里面可以放任意一个能表达键值对形式的数据结构,键是数组里面的数字,值是对应的出现次数,重写的comparator根据值来排序即可。这里我们试一下用Java 8新引进的Lambda driver(全金属狂潮的羊肉系统)。。。不对,Lambda表达式来写comparator。具体可以参见https://segmentfault.com/a/1190000009186509 之类的。。。代码如下:

    public List<Integer> topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer> countMap = new HashMap<>();
        Queue<Integer[]> countQueue = new PriorityQueue<>((a, b) -> (a[1] - b[1]));
        for (int i : nums) {
            countMap.put(i, countMap.getOrDefault(i, 0) + 1);
        }
        
        for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
            Integer num = entry.getKey();
            Integer count = entry.getValue();
            if (countQueue.size() < k || countQueue.peek()[1] < count) {
                Integer[] resultRow = {num, count};
                countQueue.add(resultRow);
            }
            
            if (countQueue.size() > k) {
                countQueue.poll();
            }
        }
        
        LinkedList<Integer> result = new LinkedList<>();
        while (!countQueue.isEmpty()) {
            result.addFirst(countQueue.poll()[0]);
        }
        
        return result;
    }

下面有一段类似的代码,但是我用另一个数据结构代替了PriorityQueue,我用的是TreeSet,但是整个流程,基本是类似的,除去一些细微的变化,我先给出代码:

    public List<Integer> topKFrequent(int[] nums, int k) {
        HashMap<Integer, Integer[]> countMap = new HashMap<>();
        TreeSet<Integer[]> countSet = new TreeSet<>((a, b) -> (a[1] != b[1] ? a[1] - b[1] : a[0] - b[0]));
        
        for (int num : nums) {
            Integer[] numCnt = countMap.getOrDefault(num, new Integer[]{num, 0});
            if (!countMap.containsKey(num)) {
                countMap.put(num, numCnt);
            }

            if (countSet.contains(numCnt)) {
                countSet.remove(numCnt);
            }
            
            numCnt[1]++;
            countSet.add(numCnt);
            if (countSet.size() > k) {
                countSet.pollFirst();
            }
        }
        
        LinkedList<Integer> result = new LinkedList<>();
        for (Integer[] numCnt : countSet) {
            result.addFirst(numCnt[0]);
        }
        
        return result;
    }

可以看得出来,第一个区别就是这个做法,我只用了一个for loop来完成的。主要目的,是为了解决这一题的延伸问题(来自dropbox貌似),如果问题是,input是一个流和不是一个固定数组的话,我们能否得到答案。流和固定数组的区别在于就是,如果我时不时加一个新的数字,我能否很快的更新新的答案。能否处理流的关键就是是否能够即时更新用于排序的数据结构,这里用PriorityQueue的方式是没办法做到的,因为PriorityQueue没办法更新放进queue里面的数据。(其实heap是可以的,每次更新数据做一个heapify就好了。但是重新的完整的写一个heap又真的很麻烦的),这个时候TreeSet就可以达到这个效果了,你可以通过logN的操作来删除TreeSet中的一个节点已经插入TreeSet中的一个节点。所以其实对比priorityqueue的做法,我只需要多一个contains和remove的步骤就可以对流进行处理了。这里TreeSet就可以很好地满足我了。

这两题的做法的复杂度都能够满足leetcode的要求就是小于O(nlogn)。which is O(nlogk)。

其实还有更好的做法,参考了这个https://leetcode.com/problems/top-k-frequent-elements/discuss/81602/Java-O(n)-Solution-Bucket-Sort

看链接就知道了,算法核心在于桶排序。也不算是一个完整的桶排序,是一个不需要进行桶内排序的桶排序。桶的设计比较粗暴但有效,桶i就表示出现了i次的数字组合,所以最多n个桶。可以和heap做法一样,先用哈希表把每个数字进行出现次数的统计,然后根据这个统计,把数字放进对应的桶里。然后再从高顺位的桶往低顺位遍历结果,一旦解集达到了k,那么我们就返回结果。代码如下:

    public List<Integer> topKFrequent(int[] nums, int k) {
        LinkedList<Integer>[] numsList = new LinkedList[nums.length];
        HashMap<Integer, Integer> countMap = new HashMap<>();
        for (int num : nums) {
            countMap.put(num, countMap.getOrDefault(num, 0) + 1);
        }
        
        for (Map.Entry<Integer, Integer> countPair : countMap.entrySet()) {
            Integer num = countPair.getKey();
            Integer count = countPair.getValue();
            if (numsList[count - 1] == null) {
                numsList[count - 1] = new LinkedList<>();
            }
            numsList[count - 1].add(num);
        }
        
        LinkedList<Integer> result = new LinkedList<>();
        for (int i = numsList.length - 1; i >= 0; i--) {
            List<Integer> counts = numsList[i];
            if (counts != null) {
                for (Integer num : counts) {
                    result.add(num);
                    if (result.size() == k) {
                        return result;
                    }
                }
            }
        }
        
        return result;
    }

这个算法的复杂度显而易见就是O(n),虽然桶是n个,但元素总数也就n个,所以算上遍历空桶,复杂度也就只有O(n)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值