美团 LeetCode 692.前K个高频单词

思路:在优先队列(堆)的基础上,增加了字典序大小的比较。使用哈希表 + 优先队列求解。

一、具体步骤:

1.使用哈希表来统计所有的词频。

2.构建大小为k的按照"词频升序 + (词频相同)字典序倒序"的优先队列:

(1)如果词频不相等,根据词频进行升序构建,保证堆顶元素是堆中词频最小的元素。

(2)如果词频相等,根据字典序的大小进行倒序构建,最终确保堆顶元素是堆中"词频最小 & 字典序最大"的元素。

3.对所有的元素进行遍历,尝试入堆:

(1)堆内元素不足K个时:直接入堆。

(2)词频大于堆顶元素时:将堆顶元素弹出,并将当前元素添加到堆中。

(3)词频小于堆顶元素时:当前元素不可能是前K大的元素,直接丢弃。

(4)词频等于堆顶元素时,根据当前元素与堆顶元素的字典序大小决定(如果字典序大小比堆顶元素要小则入堆)。

4.输出堆内元素,并翻转。

二、复杂度分析:

1.时间复杂度:

(1)使用哈希表统计词频,复杂度为O(n)。

(2)使用最多n个元素维护一个大小为k的堆,复杂度为O(nlogk)。

(3)输出答案的复杂度为O(k)(同时k <= n)。

因此整体的时间复杂度为O(nlogk)。

2.空间复杂度:O(n)。

HashMap里存放的数据是{单词String,词频Integer}。

heap里存放的数据是{词频Integer,单词String}。

附代码:

java 复制代码
class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        Map<String,Integer> map = new HashMap<>();
        // 词频统计
        for(String w : words){
            map.put(w,map.getOrDefault(w,0) + 1);
        }
        // 使用Object[]是为了不用定义新类就实现在一个数组内存放多种类型
        // 优先队列的初始容量为k,(a,b)是比较器,定义优先级规则
        PriorityQueue<Object[]> heap = new PriorityQueue<>(k,(a,b) -> {
            // 如果词频不同,按照词频升序排列(从小到大排)
            int c1 = (Integer)a[0],c2 = (Integer)b[0];
            if(c1 != c2){
                return c1 - c2;
            }
            // 如果词频相同,根据字典序倒序排列(从大到小排)
            String s1 = (String)a[1],s2 = (String)b[1];
            // s2.compareTo(s1) > 0表示s2 > s1,所以实现了倒序排列
            return s2.compareTo(s1);
        });
        for(String word : map.keySet()){
            int count = map.get(word);
            if(heap.size() < k){ // 不足k个,直接入堆
                heap.add(new Object[] {count,word});
            }else{
                Object[] peek = heap.peek();
                if(count > (Integer)peek[0]){ // 词频比堆顶元素大,弹出堆顶元素,当前元素入堆
                    heap.remove();
                    heap.add(new Object[]{count,word});
                }else if(count == (Integer)peek[0]){ // 词频与堆顶元素相同
                    String topWord = (String)peek[1];
                    if(word.compareTo(topWord) < 0){ // 且字典序大小比堆顶元素小,弹出堆顶元素,入堆
                        heap.remove();
                        heap.add(new Object[] {count,word});
                    }
                }
            }
        }
        List<String> ans = new ArrayList<>();
        while(!heap.isEmpty()){
            ans.add((String)heap.remove()[1]);
        }
        // 倒序收集,得到答案(词频从高到低排列,词频相同时字典序从低到高排列)
        // Collections.reverse()用于将List中的元素顺序反转(Collections是数组的工具箱,可以排序、反转、随机打乱、最大值、最小值、交换位置、全部填充、复制等)
        // 因为堆是最小堆,remove()出来的元素是优先级最低的元素
        Collections.reverse(ans);
        return ans;
    }
}

ACM模式:

java 复制代码
import java.util.*;

class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        Map<String, Integer> map = new HashMap<>();
        // 词频统计
        for (String w : words) {
            map.put(w, map.getOrDefault(w, 0) + 1);
        }

        // 使用Object[]是为了不用定义新类就实现在一个数组内存放多种类型
        // 优先队列的初始容量为k,(a,b)是比较器,定义优先级规则
        PriorityQueue<Object[]> heap = new PriorityQueue<>(k, (a, b) -> {
            // 如果词频不同,按照词频升序排列
            int c1 = (Integer) a[0], c2 = (Integer) b[0];
            if (c1 != c2) {
                return c1 - c2;
            }
            // 如果词频相同,根据字典序倒序排列
            String s1 = (String) a[1], s2 = (String) b[1];
            // s2.compareTo(s1) > 0表示s2 > s1,所以实现了倒序排列
            return s2.compareTo(s1);
        });

        for (String word : map.keySet()) {
            int count = map.get(word);
            if (heap.size() < k) { // 不足k个,直接入堆
                heap.add(new Object[]{count, word});
            } else {
                Object[] peek = heap.peek();
                if (count > (Integer) peek[0]) { // 词频比堆顶元素大,弹出堆顶元素,当前元素入堆
                    heap.remove();
                    heap.add(new Object[]{count, word});
                } else if (count == (Integer) peek[0]) { // 词频与堆顶元素相同
                    String topWord = (String) peek[1];
                    if (word.compareTo(topWord) < 0) { // 且字典序大小比堆顶元素小,弹出堆顶元素,入堆
                        heap.remove();
                        heap.add(new Object[]{count, word});
                    }
                }
            }
        }

        List<String> ans = new ArrayList<>();
        while (!heap.isEmpty()) {
            ans.add((String) heap.remove()[1]);
        }
        // 倒序收集,得到答案(词频从高到低排列,词频相同时字典序从低到高排列)
        Collections.reverse(ans);
        return ans;
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取单词数组(一行,用空格分隔)
        String[] words = scanner.nextLine().split(" ");

        // 读取 k 值
        int k = scanner.nextInt();

        // 计算结果
        Solution solution = new Solution();
        List<String> result = solution.topKFrequent(words, k);

        // 输出结果(按照题目要求的格式)
        System.out.println(result);

        scanner.close();
    }
}
相关推荐
地平线开发者1 小时前
量化训练时 fusebn/withbn 简介
算法·自动驾驶
不做无法实现的梦~1 小时前
MAVLink 协议教程
linux·stm32·嵌入式硬件·算法
墨白曦煜2 小时前
算法实战笔记:剥开回溯算法的外衣——从通用模板到高阶去重(八)
笔记·算法
z200509302 小时前
今日算法(回溯子集)(模版题)
数据结构·算法·leetcode
吴佳浩2 小时前
Vibe Coding 时代,研发经理为何越来越值钱?
算法·架构
IronMurphy2 小时前
【算法五十四】72. 编辑距离
算法
QiLinkOS2 小时前
【用呼吸重构创造价值关系——QiLink生态】
c语言·数据结构·c++·人工智能·单片机·嵌入式硬件·算法
妄想出头的工业炼药师2 小时前
暗光长走廊特殊场景视觉解决方案
算法·开源
weixin_468466852 小时前
图像处理特征提取新手实战指南
图像处理·人工智能·算法·ai·机器视觉·特征提取