C++前K个高频单词

这篇博客探讨了如何使用C++从给定的单词列表中,按照出现频率降序和字母顺序对前K个高频单词进行排序的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给一非空的单词列表,返回前 k 个出现次数最多的单词。

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

class Solution {
public:    
vector<string> topKFrequent(vector<string>& words, int k) 
{        
map<string, int> m;        
//统计单词出现次数        
for(const auto& wd : words)
{            
++m[wd];
}
 multimap<int, string,greater<int>> m2;
 //按照string大小遍历,字母序遍历        
 for(const auto& p : m)
 {            
 m2.insert(make_pair(p.second,p.first));        
 }
    int n = 0;        
    vector<string> str;        
    for(const auto& p : m2){            
    if(n < k){                
    str.push_back(p.second);                
    n++;            }
    else{                
    break;            
    }        
    }               
    return str;    
    }
};
<think>我们需要统计单词出现次数,然后按频率降序和字典序升序排序(注意:频率相同则按字典序,但题目要求频率由高到低,相同频率按字典序排序,这里字典序排序是指字母顺序,即小的在,所以当频率相同时,按字典序升序排列) 但是,由于题目要求返回k个,并且排序规则是:先按频率降序,频率相同则按字典序升序(即字母顺序小的在)。 我们可以使用map来统计单词出现的次数,然后考虑如何排序。由于map本身是按key(单词)排序的(字典序),但我们需要按频率排序,所以需要将map的键值对放入一个vector中,然后自定义排序函数。 步骤: 1. 使用map<string, int>统计每个单词出现的次数。 2. 将map中的元素放入vector<pair<string, int>>中。 3. 自定义排序函数:首先比较频率(int值),如果频率不等,则按频率降序排列;如果频率相等,则按单词的字典序升序排列(即字符串小的在)。 4. 排序后,取k个元素,提取单词即可。 注意:题目要求频率由高到低,相同频率按字典顺序排序。这里字典顺序排序是指从小到大的顺序(即升序)。 例如:如果两个单词频率相同,那么字典序小的排在面(这样在最终结果中,相同频率的单词会按字典序从小到大排列)。但是,由于我们最后输出是从排序后的数组取k个,所以相同频率的单词中,字典序小的会先被取出。 自定义比较函数可以这样写: bool cmp(const pair<string, int>& a, const pair<string, int>& b) { if (a.second != b.second) { return a.second > b.second; // 频率高的在 } else { return a.first < b.first; // 字典序小的在 } } 然后使用sort函数对vector进行排序,再取k个单词。 但是注意:题目要求返回的是单词列表,即vector<string>,并且只需要单词。 另外,如果k大于单词种类数,则返回所有单词(但题目没有特别说明,一般k不会超过,但为了安全,取min(k, vector.size()))。 代码实现: 示例: 输入: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2 输出: ["i", "love"] 解释: "i"和"love"为出现次数最多的两个单词,均为2次。注意"i"在字典序中比"love"小(即"i"的字典序小于"love"),所以"i"排在面。 另一种情况:如果频率相同,按字典序升序排列,那么如果多个单词频率相同,则字典序小的排在面。在排序后的数组中,相同频率的单词会按照字典序升序排列。然后我们取k个,就是取频率最高的k个,如果频率相同,则取字典序小的。 注意:如果频率相同,我们要求按字典序升序排列,那么排序函数中,当频率相同时,返回a.first < b.first,这样排序后,相同频率的单词中,字典序小的会排在面(即位置靠,因为整个数组是从大到小排序,但相同频率的组内,按字典序升序排列)。 代码: 注意:使用map统计时,遍历单词列表,每个单词计数。 我们也可以使用unordered_map来统计,这样更快,但后面需要放入vector排序。因为map(红黑树)本身有序(按key),但这里我们不需要按key排序,而是按频率和key的特定规则排序,所以用unordered_map统计也可以。 这里题目没有要求,我们可以用unordered_map提高统计效率。 步骤: 1. 用unordered_map<string, int>统计次数。 2. 将unordered_map中的键值对放入vector。 3. 自定义比较函数排序。 4. 提取k个单词。 注意:如果两个单词出现次数相同,则按字典序升序排列(即字母顺序小的在),但题目要求返回的答案按频率由高到低排序,所以相同频率的单词在结果中应该按字典序升序排列(即先输出字典序小的)。 例如:如果频率相同,那么"a"应该排在"b"的面(因为"a"字典序小),所以在排序后的数组中,相同频率的组内,字典序小的在数组的面(这样在取k个时,会先取到字典序小的)。 但是,排序函数中,我们要求整个数组是降序(频率高的在面),频率相同时,按字典序升序(即小的放面),所以这样排序后,相同频率的单词中,字典序小的会排在相同频率组的面。 因此,我们直接取k个即可。 代码实现: 注意:有可能单词列表为空,或者k为0,需要处理。 我们按照上述步骤编写代码。 另外,C++中,当使用自定义比较函数时,我们可以用lambda表达式,也可以写一个函数。 这里我们用lambda表达式。 示例代码: #include <vector> #include <string> #include <unordered_map> #include <algorithm> using namespace std; class Solution { public: vector<string> topKFrequent(vector<string>& words, int k) { // 1. 统计频率 unordered_map<string, int> countMap; for (string& word : words) { countMap[word]++; } // 2. 将map中的键值对放入vector vector<pair<string, int>> vec; for (auto& it : countMap) { vec.push_back(it); } // 3. 自定义排序 sort(vec.begin(), vec.end(), [](const pair<string, int>& a, const pair<string, int>& b) { if (a.second != b.second) { return a.second > b.second; // 频率降序 } else { return a.first < b.first; // 字典序升序 } }); // 4. 提取k个单词 vector<string> result; for (int i = 0; i < k && i < vec.size(); i++) { result.push_back(vec[i].first); } return result; } }; 但是,注意:如果使用map(而不是unordered_map)来统计,也是可以的,但统计时map是按key排序的(字典序),但后面我们还要重新排序,所以用unordered_map效率更高。 测试用例: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2 -> ["i","love"] words = ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4 -> 频率: the:4, is:3, sunny:2, day:1 -> 输出: ["the", "is", "sunny", "day"] (注意:频率相同?没有相同频率,所以按频率降序。但如果有相同频率,比如两个频率为3的,那么按字典序升序排列) 但是,考虑一个情况:如果两个单词频率相同,比如在另一个例子中,有多个频率相同的单词,那么按字典序升序排列。 例如:words = ["a", "aa", "aaa", "a", "aa", "a"],那么: a:3, aa:2, aaa:1 k=2,输出["a","aa"] 另一个例子:words = ["a","b","c","a","b","b"],那么: a:2, b:3, c:1 k=2,输出["b","a"] (因为b频率3>a频率2) 再一个例子:words = ["a","a","b","b","c","c","d"], k=2,那么a,b,c频率都是2,d频率1。那么按相同频率的字典序升序排列:a, b, c(因为a<b<c),所以取2个是a和b。 因此,输出["a","b"]。 但是,注意:排序后数组是:先按频率降序,所以频率2的都在面(三个频率2的),然后这三个中按字典序升序排列,所以顺序是a, b, c。然后取两个就是a和b。 因此,代码正确。 然而,上述代码在排序时,如果数据量很大,我们可以考虑使用堆(优先队列)来优化,只维护k个元素,这样时间复杂度为O(nlogk),但题目没有特别要求,我们使用排序的方法(O(nlogn))在大多数情况下也是可以接受的。 但是,题目要求用map写,我们这里用了unordered_map,但map也是可以的,只需要将unordered_map换成map。不过,题目没有明确要求使用哪种map,所以用unordered_map更高效。 如果题目要求必须用map,那么我们可以用map,但map在统计时并没有优势(因为按key排序了,但我们并不需要这个排序),所以这里我们按照题目的要求“用map写”,我们可以理解为使用关联容器,但为了清晰,我们可以使用map,但效率略低(因为插入是O(logn)每次,而unordered_map是平均O(1))。 不过,题目没有明确说明,我们使用unordered_map也是可以的。但为了严格遵循题目要求“用map写”,我们可以使用map(但题目要求“用map写”可能是指使用关联容器,包括map和unordered_map?)。这里我们按照通常的理解,题目要求使用map,我们就用map,但注意map在统计时是按key排序的,不过不影响最终结果,因为后面我们还要重新排序。 所以,为了满足题目要求,我们使用map(即std::map)来统计。 修改:将unordered_map改为map。 但是,实际上,用map和unordered_map在后续处理上是一样的。 因此,代码可以修改为: #include <vector> #include <string> #include <map> #include <algorithm> using namespace std; class Solution { public: vector<string> topKFrequent(vector<string>& words, int k) { map<string, int> countMap; for (string& word : words) { countMap[word]++; } vector<pair<string, int>> vec(countMap.begin(), countMap.end()); // 可以直接用迭代器构造 // 排序,注意:map本身是按key排序的(字典序),但这里我们不需要这个顺序,因为后面要按频率重新排序 sort(vec.begin(), vec.end(), [](const auto& a, const auto& b) { if (a.second != b.second) return a.second > b.second; else return a.first < b.first; }); vector<string> res; for (int i = 0; i < k; i++) { res.push_back(vec[i].first); } return res; } }; 注意:这里使用map统计,那么vec中的元素在未排序是按key的字典序升序排列的(因为map的有序性)。然后我们按频率和字典序重新排序,所以没问题。 但是,有一个小问题:在自定义排序中,当频率相同时,我们要求按字典序升序排列。而原本map中的顺序已经是字典序升序了,但我们在排序时又指定了相同频率按字典序升序,所以结果正确。 但是,在排序算法中,我们使用的排序是稳定的吗?不是,因为sort是不稳定的,但我们的比较函数已经同时考虑了频率和字典序,所以即使不稳定,排序后也能满足要求。 因此,这样实现即可。 但是,我们也可以考虑使用稳定排序,但没必要,因为我们的比较函数已经定义了严格的顺序。 所以,最终代码使用map,并按照上述方式排序。 但是,注意:题目要求“用map写”,我们使用了map,并且代码如上。 然而,我们也可以使用优先队列(堆)来避免全排序,但题目没有要求效率,且k可能接近n,所以全排序也是可以接受的。 因此,我们提供上述代码。 注意:如果k很小,而不同的单词数n很大,那么使用堆(优先队列)会更好。这里我们根据题目要求,没有特别说明,所以我们用全排序。 用堆的方法(额外提供,但不是题目要求的,题目要求用map,我们主要用map统计,然后排序,所以用全排序即可): 使用最小堆(优先队列),维护一个大小为k的堆。堆的比较规则:按频率升序,如果频率相同,则按字典序降序(这样堆顶就是频率最小且字典序最大的元素,当有新元素时,如果新元素频率大于堆顶,则弹出堆顶,加入新元素;或者频率相同但字典序更小(即字典序升序中更靠)则替换)。 但是,这样比较麻烦,而且题目要求用map,我们使用全排序简单明了。 因此,我们使用全排序。 最后,返回结果。 注意:题目要求返回的答案按频率由高到低排序,相同频率按字典顺序排序(即字典序升序)。所以我们的结果数组已经是这样排序了,直接取k个即可。 所以,代码完成。 但是,我们考虑一个边界:k可能大于vec.size(),所以循环中我们取min(k, vec.size()),但代码中已经用i<k && i<vec.size(),或者我们直接循环k次,但k可能大于vec.size(),所以用: for (int i = 0; i < k && i < vec.size(); i++) 或者,我们可以在循环取k = min(k, (int)vec.size())。 这里我们采用第一种。 因此,最终代码如上述。 现在,我们按照题目要求,在最后输出§§相关问题§§,并附上3-5个相关问题。 注意:问题要基于当问题和回答。 相关问题可以涉及:效率优化(堆)、其他语言实现、处理大数据集的方法、排序稳定性等。 例如: 1. 如果单词列表非常大(例如数亿个单词),如何优化上述算法以减少内存使用? 2. 在自定义排序函数中,为什么当频率相同时要按字典序升序排列?与题目要求的关系是什么? 3. 除了使用vector和sort,能否使用优先队列(堆)来得到k个高频单词?如何实现? 4. 在C++中,map和unordered_map在统计单词频率时的主要区别是什么?哪种更适合此场景? 5. 如果要求按频率降序排序,但相同频率的单词按出现顺序排序(即稳定排序),应如何修改? 我们选择3-5个。 因此,最终回答如下:</think>### 解法思路 使用 `map` 统计单词频率,然后通过自定义排序实现以下要求: 1. **频率降序**:出现次数多的单词面 2. **字典序升序**:频率相同时,字母顺序小的单词面 3. **取 k 个**:截取排序后的 k 个结果 ### 完整代码实现(C++) ```cpp #include <vector> #include <string> #include <map> #include <algorithm> using namespace std; class Solution { public: vector<string> topKFrequent(vector<string>& words, int k) { // 1. 使用map统计单词频率 map<string, int> freqMap; for (const string& word : words) { freqMap[word]++; } // 2. 将map数据转存到vector vector<pair<string, int>> vec(freqMap.begin(), freqMap.end()); // 3. 自定义排序:先按频率降序,再按字典序升序 sort(vec.begin(), vec.end(), [](const auto& a, const auto& b) { if (a.second != b.second) return a.second > b.second; // 频率降序 else return a.first < b.first; // 字典序升序 }); // 4. 提取k个单词 vector<string> result; for (int i = 0; i < k && i < vec.size(); i++) { result.push_back(vec[i].first); } return result; } }; ``` ### 关键步骤说明 | 步骤 | 操作 | 说明 | |------|------|------| | 1 | `map<string, int>` 统计 | 利用map自动按key字典序排序的特性 | | 2 | 转存到 `vector<pair>` | 为后续自定义排序做准备 | | 3 | 自定义排序 | `a.second > b.second` 实现频率降序<br>`a.first < b.first` 实现字典序升序 | | 4 | 截取k个 | 处理k大于单词种类数的边界情况 | ### 示例测试 **输入**: ```cpp words = ["i", "love", "leetcode", "i", "love", "coding"] k = 2 ``` **输出**: ``` ["i", "love"] ``` **排序过程**: 1. 统计频率:`{"i":2, "love":2, "leetcode":1, "coding":1}` 2. 排序后顺序:`["i", "love", "coding", "leetcode"]` (频率相同时按字典序升序:"coding" < "leetcode",但"i" < "love") 3. 取2个:`["i", "love"]` ### 优化方案 对于海量数据场景: 1. 改用 `unordered_map` 统计(O(1) vs map的O(log n)) 2. 使用最小堆(priority_queue)维护TopK,时间复杂度降至O(n log k) ```cpp // 最小堆实现代码片段 auto cmp = [](const auto& a, const auto& b) { return a.second == b.second ? a.first < b.first : a.second > b.second; }; priority_queue<pair<string, int>, vector<pair<string, int>>, decltype(cmp)> pq(cmp); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值