前k个高频单词

简介: 前k个高频单词

leetcode之前k个高频单词(难度:中等)


题目要求

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。


示例 1:

输入: words = [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2

输出: [“i”, “love”] 解析: “i” 和 “love” 为出现次数最多的两个单词,均为2次。

注意,按字母顺序 “i” 在 “love” 之前。


示例 2:

输入: [“the”, “day”, “is”, “sunny”, “the”, “the”, “the”, “sunny”, “is”,

“is”], k = 4 输出: [“the”, “is”, “sunny”, “day”] 解析: “the”, “is”,

“sunny” 和 “day” 是出现次数最多的四个单词,

出现次数依次为 4, 3, 2 和 1 次。

class Solution {
    public List<String> topKFrequent(String[] words, int k) {
    }
}

做题思路

首先,当我们看到题目TopK的问题时,就会想到使用堆来解决,如果大家不了解堆,可以看看我的这篇文章什么是堆以及堆是怎样创建的如果TopK是前K个最大的值,那么就需要使用构建最小堆来解决,如果TopK是前K个最小的值,那么就需要构建最大堆来解决。很显然这道题是求前K个高频单词,所以我们构建最小堆来解决问题。


在知道了使用什么结构来解决,我们还需要知道,怎样才能知道每个单词出现的次数呢?很多人第一想法可能是通过创建一个类来实现,类里面有这个单词以及单词出现的次数,但是太麻烦了,不推荐。那么怎样做才最简单呢?那就是使用我们的HashMap的Key — Value模型,将单词作为Key,次数作为Value。


在统计完每个单词出现的次数之后,我们就创建k个大小的最小堆,每次进堆就与最小堆的根节点所代表的单词出现的次数比较,如果待进堆的单词出现的次数大于堆的根节点单词出现的次数,那么就先出堆,然后再将待插入的数据入堆,调整最小堆。


当HashMap中的数据都遍历完成后,我们就需要将最小堆中的k个数据出堆,我们都知道每次从最小堆中出来的都是堆顶的也就是堆中最小的数据,但是我们再仔细看题目要求可以知道按单词出现的频率的高低排序。所以我们需要先将HashMap中的数据都插入到一个顺序表中,然后再将整个顺序表逆序一次就得到了我们想要的结果。

image.png

代码实现

统计单词出现的次数

  Map<String,Integer> hashmap = new HashMap<>();
    for(String s : words) {
        if(hashmap.get(s) == null) {
            hashmap.put(s,1);
        }else {
            hashmap.put(s,hasnmap.get(s) + 1);
        }
    }

创建大小为k的最小堆

因为Java提供的PriorityQueue默认是最小堆,所以我们可以直接使用Java提供的方法。先将k个数据放入最小堆中,后面再放入数据的时候,需要比较待插入的单词出现的次数与堆顶单词出现次数的大小,如果待插入的单词出现的次数大于堆顶单词出现的数据,那么就先出堆,然后再将这个单词入堆;如果待插入单词出现的次数等于堆顶单词出现的次数,那么就需要比较带插入单词与堆顶单词的字典顺序了,如果待插入单词的字典顺序在前面,那么就先出堆然后再入堆,反之就不需要对对做出调整。

//创建k个大小的最小堆
//我们这里传入一个比较器并重写compare方法,因为Java提供的compare方法的比较是基本数据类型之间的
PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(
            k,new Comparator<Map.Entry<String, Integer>>() {
                @Override
                public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                        return o1.getValue().compareTo(o2.getValue());
                    }
                }
        );
for(Map.Entry<String,Integer>  entry: hashmap.entrySet()) {
    //先在堆中存放k个节点
    if(minHeap.size() < k) {
        minHeap.offer(entry);
    }else {
        if(minHeap.peek().getValue().compareTo(entry.getValue()) < 0) {
          minHeap.poll();
            minHeap.offer(entry);
        }else if(minHeap.peek().getValue().compareTo(entry.getValue()) == 0) {
            //当出现的次数相同时就比较单词的字典顺序
          if(minHeap.peek().getKey().compareTo(entry.getKey()) > 0) {
                minHeap.poll();
              minHeap.offer(entry);
          }
      }
    }
}

这里我们的比较器构造的有问题,我们先看会出现什么问题

16.png

当k为3的时候为什么会出错呢

17.png


所以我们的构造器还需要添加一个条件,当单词出现的次数相同时就比较单词出现的字典顺序。


修改构造器

//创建k个大小的最小堆
//我们这里传入一个比较器并重写compare方法,因为Java提供的compare方法的比较是基本数据类型之间的
PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(
            k,new Comparator<Map.Entry<String, Integer>>() {
                @Override
                public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                        if(o1.getValue().compareTo(o2.getValue()) == 0) {           //因为堆顶的节点先出,后面再逆序后就被调整到后面了,所以我们将字典序靠后的放在堆顶
                            return o2.getKey().compareTo(o1.getKey());
                        }
                        return o1.getValue().compareTo(o2.getValue());
                    }
                }
        );
for(Map.Entry<String,Integer>  entry: hashmap.entrySet()) {
    //先在堆中存放k个节点
    if(minHeap.size() < k) {
        minHeap.offer(entry);
    }else {
        if(minHeap.peek().getValue().compareTo(entry.getValue()) < 0) {
          minHeap.poll();
            minHeap.offer(entry);
        }else if(minHeap.peek().getValue().compareTo(entry.getValue()) == 0) {
            //当出现的次数相同时就比较单词的字典顺序
          if(minHeap.peek().getKey().compareTo(entry.getKey()) > 0) {
                minHeap.poll();
              minHeap.offer(entry);
          }
      }
    }
}

将最小堆的数据插入到顺序表中并逆序

List<String> list = new ArrayList<>();
        //3.将堆中的数据放入数组中
for(int i = 0; i < k; i++) {
    String ret = minHeap.poll().getKey();
    list.add(ret);
}
//List继承自collections类,collections类中实现了reverse逆序方法
Collections.reverse(list);
return list;

整体代码展示

class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        //1.统计每个单词出现的次数
        Map<String,Integer> hashmap = new HashMap<>();
        for(String s : words) {
            if(hashmap.get(s) == null) {
                hashmap.put(s,1);
            }else {
                hashmap.put(s,hashmap.get(s) + 1);
            }
        }
        //创建k个大小的最小堆
        //这里记得重写compare方法
        PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(
                new Comparator<Map.Entry<String, Integer>>() {
                    @Override
                    public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                        if(o1.getValue().compareTo(o2.getValue()) == 0) {           //因为堆顶的节点先出,后面再逆序后就被调整到后面了,所以我们将字典序靠后的放在堆顶
                            return o2.getKey().compareTo(o1.getKey());
                        }
                        return o1.getValue().compareTo(o2.getValue());
                    }
                }
        );
        for(Map.Entry<String,Integer>  entry: hashmap.entrySet()) {
            //先在堆中存放k个节点
            if(minHeap.size() < k) {
                minHeap.offer(entry);
            }else {
                if(minHeap.peek().getValue().compareTo(entry.getValue()) < 0) {
                    minHeap.poll();
                    minHeap.offer(entry);
                }else if(minHeap.peek().getValue().compareTo(entry.getValue()) == 0) {
                    //当出现的次数相同时就比较单词的字典顺序
                    if(minHeap.peek().getKey().compareTo(entry.getKey()) > 0) {
                        minHeap.poll();
                        minHeap.offer(entry);
                    }
                }
            }
        }
        List<String> list = new ArrayList<>();
        //3.将堆中的数据放入数组中
        for(int i = 0; i < k; i++) {
            String ret = minHeap.poll().getKey();
            list.add(ret);
        }
        Collections.reverse(list);
        return list;
    }
}

18.png

相关文章
|
安全 数据安全/隐私保护
CSRF漏洞利用工具 -- CSRFTester
CSRF漏洞利用工具 -- CSRFTester
487 0
|
ARouter 索引
|
存储 关系型数据库 MySQL
MySQL的一些主要用途
【8月更文挑战第31天】MySQL的一些主要用途
677 2
|
11月前
|
人工智能 自然语言处理 算法
Devika AI:开源的 AI 软件开发工具,理解和执行复杂的人类指令
Devika AI 是一款开源的 AI 软件开发工具,能够理解和执行复杂的人类指令。它通过分解任务、信息搜集和代码生成,帮助开发者提高效率,减少人工干预。本文将详细介绍 Devika AI 的功能、技术原理以及如何运行和配置该工具。
477 9
Devika AI:开源的 AI 软件开发工具,理解和执行复杂的人类指令
|
11月前
|
机器学习/深度学习 搜索推荐 API
淘宝/天猫按图搜索(拍立淘)API的深度解析与应用实践
在数字化时代,电商行业迅速发展,个性化、便捷性和高效性成为消费者新需求。淘宝/天猫推出的拍立淘API,利用图像识别技术,提供精准的购物搜索体验。本文深入探讨其原理、优势、应用场景及实现方法,助力电商技术和用户体验提升。
|
缓存 监控 数据库
性能优化的常见策略有哪些
【10月更文挑战第20天】性能优化的常见策略有哪些
631 0
|
运维 关系型数据库 MySQL
"MySQL运维精髓:深入解析数据库及表的高效创建、管理、优化与备份恢复策略"
【8月更文挑战第9天】MySQL是最流行的开源数据库之一,其运维对数据安全与性能至关重要。本文通过最佳实践介绍数据库及表的创建、管理与优化,包括示例代码。涵盖创建/删除数据库、表结构定义/调整、索引优化和查询分析,以及数据备份与恢复等关键操作,助您高效管理MySQL,确保数据完整性和系统稳定运行。
884 0
|
图形学
IBM SPSS Amos下载与安装
IBM SPSS Amos下载与安装
1552 1
|
运维 微服务
业务系统架构实践问题之什么是配置态和运行态的解耦
业务系统架构实践问题之什么是配置态和运行态的解耦
340 0
|
缓存 监控 前端开发
Performance Optimization
Performance Optimization
3365 2