「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」
题目介绍
给一非空的单词列表,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
示例1
输入: ["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 次。
注意:
- 假定 k 总为有效值, 1 ≤ k ≤ 集合元素数。
- 输入的单词均由小写字母组成。
解题思路
解决这道题的方法不难,首先遍历所有的单词,然后统计每个单词出现的频率,按顺序进行排序,然后返回次数最多的 k 个单词就行了
需要注意的是,当单词出现的次数一样多时,需要根据字母的顺序排序。在 js 中对单词进行比较是将每个字母都转换成 ASCII 码进行比较,相同位置的字母依次比较,直到出现不同字母的位置为止,而且字母之间不能直接通过相减的方式比较两个字母的大小关系,但是可以通过 “>”、 “<” 来比较两个字母的大小关系,也可以使用 js 中自带的 localeCompare 方法来比较两个单词的大小关系。
思路一:哈希表 + 直接排序
解题步骤:
- 遍历所有的单词,将单词出现的次数保存到哈希表
wordsMap中 - 将哈希表中的所有键加入到数组
ans中 - 将数组
ans根据题目的排序要求进行从大到小排序 - 取
ans数组的前k项即为当前题目的结果
var topKFrequent = function(words, k) {
// 创建一个保存单词出现频次的哈希表
const wordsMap = new Map()
// 遍历所有单词,保存单词出现频次
words.forEach(v => {
// 如果第一次出现该单词,次数设置为 0
if (!wordsMap.has(v)) wordsMap.set(v, 0)
// 将单词出现的次数 +1
wordsMap.set(v, wordsMap.get(v) + 1)
})
// 创建一个存放哈希表的键
const ans = []
for(const key of wordsMap.keys()) {
ans.push(key)
}
// 按题目要求的顺序从大到小进行排序
ans.sort((a, b) => {
if (wordsMap.get(a) !== wordsMap.get(b)) return wordsMap.get(b) - wordsMap.get(a)
return a.localeCompare(b)
})
// 返回排序后数组的前 k 项
return ans.slice(0, k)
};
思路二:哈希表 + 小顶堆
对于这种求最大 k 个元素的问题,最好的方式是使用堆来做,因为题目是要求前 k 个频次最高的单词,因此我们可以用一个大小为 k 的小顶堆来维护频次最高的 k 个单词,最后将堆中的单词依次弹出,即可得到最终结果
解题步骤:
- 遍历所有的单词,将单词出现的次数保存到哈希表
wordsMap中 - 创建一个大小为
k的小顶堆,当小顶堆的大小小于 k 时,直接向小顶堆插入数据,向上调整,否则,当待插入数据大于堆顶元素时,将对顶元素替换为待插入的元素,向下调整 - 遍历完整个哈希表的数据,依次弹出堆顶元素
- 从结果数组头部插入弹出元素
- 返回结果数组
var topKFrequent = function(words, k) {
// 统计单词的频次插入到哈希表中
const wordsCount = new Map()
words.forEach(v => {
if (!wordsCount.has(v)) wordsCount.set(v, 0)
wordsCount.set(v, wordsCount.get(v) + 1)
})
// 将哈希表中的键值对依次插入到小顶堆中
const heap = new Heap(k)
for(const [key, value] of wordsCount.entries()) {
heap.push([key, value])
}
// 依次弹出小顶堆的堆顶元素,从头部插入结果数组中
const ans = []
while(heap.size()) {
ans.unshift(heap.pop()[0])
}
return ans
};
class Heap{
constructor(k) {
this.heap = []
this.k = k
}
// 返回当前堆的大小
size() {
return this.heap.length
}
// 插入元素
// 如果当前堆的大小小于 k 值,从堆尾插入,向上调整
// 如果当前堆的大小等于 k 值,如果待插入元素大于堆顶元素,替换堆顶元素的位置,向下调整
push(val) {
if (this.size() < this.k) {
this.heap.push(val)
this.sortBack()
} else if (this.compare(val, this.heap[0])) {
this.heap[0] = val
this.sortFront()
}
}
// 从堆顶弹出元素,然后将尾部元素移动到堆顶,向下调整
pop() {
const val = this.heap[0]
const back = this.heap.pop()
if (this.heap.length) {
this.heap[0] = back
this.sortFront()
}
return val
}
// 向上调整堆结构
sortBack() {
let i = this.size() - 1
while (i > 0 && this.compare(this.heap[Math.floor((i - 1) / 2)], this.heap[i])) {
[this.heap[i], this.heap[Math.floor((i - 1) /2)]] = [this.heap[Math.floor((i - 1) /2)], this.heap[i]]
i = Math.floor((i - 1) / 2)
}
}
// 向下调整堆结构
sortFront() {
let i = 0
while (i * 2 + 1 < this.size()) {
let temp = i
if (this.compare(this.heap[temp], this.heap[i * 2 + 1])) temp = i * 2 + 1
if (i * 2 + 2 < this.size() && this.compare(this.heap[temp], this.heap[i * 2 + 2])) temp = i * 2 + 2
if (temp === i) break
[this.heap[i], this.heap[temp]] = [this.heap[temp], this.heap[i]]
i = temp
}
}
// 根据题目规则比较元素之间的大小
compare(a, b) {
if (a[1] !== b[1]) return a[1] > b[1]
return a[0] < b[0]
}
}