LeetCode 192. 统计词频
题目描述

题意分析
从
words.txt读取由小写字母组成的单词,单词之间有一个或多个空白分隔。要求统计每个单词的总出现次数,并按照词频降序输出单词 次数。题目保证各词频率不同,因此不需要额外规定并列顺序。输入按行组织,但统计对象是单词;相同单词还可能散落在不同位置。需要先拆成一词一行,再让相同词相邻,才能交给只统计连续重复行的
uniq -c。最后按计数重新排序,而不是保留字典序。
解法:拆词、聚合、按频次排序
核心思路
[!blue]
整条管道依次改变记录的含义,每一级的输出正好符合下一级的输入要求:
tr -s '[:space:]' '\n'将空白转换成换行,并压缩连续换行,得到一词一行。文件开头若有空白,仍可能形成空行,所以接上awk 'NF',只保留字段数非零的记录。- 第一次
sort按单词排序,使同一个词的所有出现位置连续。uniq -c才能一次数完整组,输出次数 单词。sort -k1,1nr以第一列为唯一排序键,n表示按数值比较,r表示逆序,也就是按频次从大到小。- 最后的
awk '{print $2, $1}'调换两列,使用默认空格分隔,输出题目要求的格式。两次排序不可混为一次:前一次是在计数前聚集相同单词,后一次是在计数后决定输出顺序。计数尚未产生时,没有可供按频次排序的列。
解题步骤
- 通过
< words.txt将文件送给tr,把空白分隔的文本拆成单词记录。- 过滤空记录,避免把空白当作一个单词。
- 按词排序并用
uniq -c计数,此时每个不同单词恰好对应一行。- 按计数列做数值降序排序,再交换计数与单词两列。
样例计数后为
day 1、is 3、sunny 2、the 4这四组词频,最终按照 4、3、2、1 的顺序输出。文件没有有效单词时,空记录被过滤,后续管道自然没有输出。
代码实现
tr -s '[:space:]' '\n' < words.txt \
| awk 'NF' \
| sort \
| uniq -c \
| sort -k1,1nr \
| awk '{print $2, $1}'
复杂度分析
设总字符数为 $N$,单词总数为 $W$,不同单词数为 $U$。拆词、计数和格式化需要线性扫描,主要开销来自两次排序。
- 时间复杂度:若一次排序比较按常数成本计,整体为 $O(N + W\log(W+1) + U\log(U+1))$。若单词很长,还需要计入字典序比较实际读取的字符数。
- 空间复杂度:中间记录与排序数据的总规模为 $O(N)$;
sort可以采用外部排序,将部分中间数据放在临时磁盘上,因此不能把所有排序存储都简单视为常驻内存。
关键点总结
[!green]
uniq的“重复”指相邻行重复,先排序才能得到整个文件的总词频。uniq -c将计数放在第一列,最终必须交换成单词在前。- 最终排序必须是数值降序,字符串字典序无法正确处理两位数及更大的计数。
- 代码中的反斜杠只用于跨行展示,整个过程仍是一条 Unix 管道。
易错点总结
[!yellow]
- 不拆词就直接排序:排序与计数的对象会变成整行文本,而不是单词。
- 不排序就执行
uniq -c:同一个词若被别的词隔开,会分成多个计数组,无法得到总频次。- 不排除空记录:输入开头或连续空白可能让空行参与计数,产生并不存在的单词。
- 第二次排序不加
n:词频会按字符顺序比较,可能把 10 排在 2 后面。- 交换列后仍按第一列做数值排序:第一列已经是单词,应先按计数排序,再格式化输出。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 692. 前K个高频单词 | 中等 | 同样统计词频并按频次排序,原题只返回前K项,本题输出全部不同词及次数。 |
| 451. 根据字符出现频率排序 | 中等 | 同样先聚合频次再排序,本题对象是单词,原题对象是字符。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!