前言
本文记录4月6日晚7点一场软件开发岗笔试的题目,思路以及代码实现。
一、题目简介
题目:
查找舆情热词
具体描述:
输入正整数topN和文章数M,正整数topN表示要找出来的出现频率最高的topN个字符串,M篇文章中每篇文章会有两个字符串,一个是标题字符串,一个是正文字符串,字符串间有空格,每个单词被空格隔开。我们的目的就是把这M篇文章连标题带正文拆成一个个单词,然后统计这一堆单词出现频率最高的topN个。
统计规则:标题中出现的词语频率系数为3,正文中出现的词语频率系数为1,返回的答案应该按照词语出现从高到低排序,当词语出现次数频率相同时,在标题中出现频率次数高的排在前面,如果仍然相同,则按照词语在标题中出现的先后顺序进行排序,如果仍相同,则按照词语在正文中出现的先后顺序进行排序,先出现的排在前面。
输入输出:
输入:第一行输入为正整数topN和文章数M。然后由于每篇文章有标题和正文两行,因此后面有2*M行数据。从第二行起,按顺序处理每篇文章的标题串和正文串。
输出:出现频率topN高的单词,每个单词用‘ ’隔开。
二、思路
求topK,常用思路就是用堆实现,本题由于要统计次数和出现顺序因此还要用一大堆哈希表,对于这个问题,本人首先想到使用小根堆实现,先把这几个哈希表弄好,然后维护一个容量不超过topN的小根堆,堆中存放着出现频率topN的单词,堆顶元素即为出现频率排第N位的单词,从total_fre这个大哈希表里依次取单词往堆里压,每次判断新单词和堆顶元素的出现频率大小关系,若新单词出现频率大于堆顶元素则弹出堆顶元素并将这个元素压入堆,否则堆不变,最后将堆中元素依次弹出再反转一下就是出现次数从高到低的topN个元素啦。(这是我最初想到的一种空间复杂度相对理想的完美思路)
但是!由于本题中出现频率的比较规则过于复杂,在给堆定义了cmp后还得再写函数去比较新单词和堆顶元素的出现频率大小关系什么的,这非常麻烦,而且仔细思考后发现和这么多哈希表相比这小根堆其实省不了多少空间,干脆用大根堆,新单词来了也不用判断啥了,直接压进去就完事,最后弹topN次就行了。
三、C++代码实现
#include <iostream>
#include <cstring>
#include <unordered_map>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
int topn, m;
unordered_map<string, int> total_fre; // 维护每个单词在标题和正文中的出现总次数(已按(3,1)加权)
unordered_map<string, int> title_fre; // 维护每个单词在标题中的出现次数
unordered_map<string, int> first_in_title; // 维护每个单词在标题中首次出现的位置
unordered_map<string, int> first_in_text; // 维护每个单词在正文中首次出现的位置
struct cmp
{
bool operator()(pair<string, int> &p1, pair<string, int> &p2)
{
// 比较规则0:比单词在标题和正文中出现的总次数
if (p1.second != p2.second) return p1.second < p2.second;
// 比较规则1:比单词在标题中出现的次数
else if (title_fre[p1.first] != title_fre[p2.first])
return title_fre[p1.first] < title_fre[p2.first];
// 比较规则2:比单词在标题中出现的顺序
else if (first_in_title[p1.first] != first_in_title[p2.first])
return first_in_title[p1.first] < first_in_title[p2.first];
// 比较规则3:比单词在正文中出现的顺序
else
return first_in_text[p1.first] < first_in_text[p2.first];
}
};
priority_queue<pair<string, int>, vector<pair<string, int>>, cmp> pq; // 自定义大根堆,用于存储所有pair<string, int>
int main()
{
cin >> topn >> m; // 输入正整数topN和文章数M
string lineStr;
getline(cin, lineStr); // 换行
while (m--)
{
string title, text;
getline(cin, title); // 输入标题
for (int i = 0, j = 0, index = INT32_MAX; i <= title.size(); i++)
{
if (i == title.size() || title[i] == ' ')
{
string str = title.substr(j, i - j);
j = i + 1;
total_fre[str] += 3;
title_fre[str] += 1;
if (first_in_title.count(str) == 0)
first_in_title[str] = index--;
}
else continue;
}
getline(cin, text); // 输入正文
for (int i = 0, j = 0, index = INT32_MAX; i <= text.size(); i++)
{
if (i == text.size() || text[i] == ' ')
{
string str = text.substr(j, i - j);
j = i + 1;
total_fre[str] += 1;
if (first_in_text.count(str) == 0)
first_in_text[str] = index--;
}
else continue;
}
}
for (auto it = total_fre.begin(); it != total_fre.end(); it++)
{
pq.push(pair<string, int>{it->first, it->second}); // 所有单词压入大根堆,直接pop就是按序输出
}
while (topn--)
{
cout << pq.top().first << ' ';
pq.pop();
}
return 0;
}
总结
菜鸡变成卷王的第一步!