数据结构-再谈Topk问题

文章介绍了如何使用Java中的优先级队列和Map解决TopK问题,具体包括找出数组中出现频率前k高的元素和单词列表中出现次数最多的k个单词。通过扫描数组、存储频率、利用小堆进行排序并处理相同频率的情况来实现解决方案。
摘要由CSDN通过智能技术生成

Topk问题是Java中经典的一类问题,在浅谈TopK部分已经介绍了一道经典的面试题:最小的K个数,主要是通过优先级队列来实现的。前面介绍过了元素的比较Map和Set的基础用法,接下来看一下两道稍微复杂的TopK问题相关的题目。

前k个高频元素

1、描述: 给你一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。你可以按任意顺序返回答案。
在这里插入图片描述
2、思路:

取前K个频率最高的元素,立马想到用小堆(取大用小),但是这时候需要通过元素出现的频率来判断大小关系,所以需要使用Map集合保存一对元素,以及告诉JDK元素之间的大小关系。首先扫描整个nums数组,将每个元素和它出现的次数保存在Map接口中,然后扫描Map接口,将出现频次最高的前k个对象保存到优先级队列中,最后从对列中取出这几个对象的key值就是最终的结果。

3、代码

public class Num347_TopKFrequent {
    public int[] topKFrequent(int[] nums, int k) {
        //自定义局部内部类
        class Freq{
            int key;
            int times;//出现频次

            public Freq(int key, int times) {
                this.key = key;
                this.times = times;
            }
        }
        //1、扫描这个数组,将数组元素和出现的频次保存在map接口中
        Map<Integer,Integer> numTimes = new HashMap<>();
        for(int i : nums){
//            numTimes.put(i,numTimes.getOrDefault(i,0) + 1);
            if(numTimes.containsKey(i)){
                //如果此时key值在numtimes中已经存在
                //新的频次就是原始的频次value + 1
                //先将原始频次取出
                int value = numTimes.get(i);
                //再将value值加1塞回map中
                numTimes.put(i,value + 1);
            }else{
                //此时key值在map中第一次出现
                //将当前key对应的value置为1
                numTimes.put(i,1);
            }
        }
        //2、扫描Map接口。将出现频次最高的前k个Freq对象保存到优先级队列中
        Queue<Freq> queue = new PriorityQueue<>(new Comparator<Freq>() {
            @Override
            public int compare(Freq o1, Freq o2) {
                //定义比较的方式
                return o1.times - o2.times;
            }
        });
        //需要将Map转为set才能遍历
        for(Map.Entry<Integer,Integer> entry:numTimes.entrySet()){
            queue.offer(new Freq(entry.getKey(),entry.getValue()));
            if(queue.size() > k){
                queue.poll();
            }
        }
        //3、从队列中取出这k个freq的Key值就是所求结果
        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = queue.poll().key;
        }
        return result;
    }
}

前k个高频单词

1、描述:给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序排序。
在这里插入图片描述
2、思路:

这个题的整体思路和上面的题目差不多,但是有多次需要反转的地方。取大用小堆,这是首先能想到的。返回的答案应该按单词出现频率由高到低排序?这个怎么实现?小堆的最后结果保存了出现频次最高的前3个字母,但小堆元素默认是升序出队的,所以我们需要一个方法让他倒着打印。使用链表的头插来保存元素。如果不同的单词有相同出现频率, 按字典顺序 排序?也就是说字典序小的需要排在字典序大的前面(比如“i”“l”之前),但是最终选择用头插来保存元素,这样的话字典序大的就保存到了字典序小的前面。所以此时需要让JDK觉得字典序大的在字典序小的前面,头插之后,字典序小的就保存在了字典序大的前面。

3、代码实现

public class Num692_TopKFrequent {
    public List<String> topKFrequent(String[] words, int k) {
        //1、首先用Map来保存单词及其出现的频次
        Map<String,Integer> wordsTable = new HashMap<>();
        for(String str : words){
            if(wordsTable.containsKey(str)){
                //将频次取出
                int times = wordsTable.get(str);
                //再塞入Map中
                wordsTable.put(str,times + 1);
            }else{
                //此时Map中不包含str,第一次出现
                wordsTable.put(str,1);
            }
        }
        //2、此时wordsTable保存了每个不重复单词及其出现的频次
        //先定义优先级对列判定优先级,取大用小
        Queue<String> queue = new PriorityQueue<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //先按照出现频次来判定
                int o1Times = wordsTable.get(o1);
                int o2Times = wordsTable.get(o2);
                //频次相同按照字典序判断
                //由于最终要使用头插法保存元素,所以字典序大的要排在前面
                return o1Times == o2Times? o2.compareTo(o1) : o1Times - o2Times;
            }
        });
        //3、扫描Map集合,保存出现频次最高的前K个单词
        for(Map.Entry<String,Integer> entry : wordsTable.entrySet()){
            queue.offer(entry.getKey());
            if(queue.size() > k){
                queue.poll();
            }
        }
        //4、此时队列中就保存了出现频次最高的前k个单词,输出保存
        LinkedList<String> result = new LinkedList<>();
        while (!queue.isEmpty()){
            //现在是小堆,出队时按照出现频次右低到高出队的
            //所以此时要将每个出队元素头插到链表中来保存最后结果
            result.addFirst(queue.poll());
        }
        return result;
    }
}

上面两道题目涵盖了优先级队列、Map的使用以及元素的比较相关的知识点,综合性较强,属于TopK问题中相对复杂的问题。遇到这类问题,可以先将每一步的解决思路写下来,然后对每一步做代码的实现,这样问题就可以被拆分,然后逐步攻克即可。

继续加油努力!!!
在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值