Codeup Contest ID:100000599
题目描述
People often have a preference among synonyms of the same word. For example, some may prefer “the police”, while others may prefer “the cops”. Analyzing such patterns can help to narrow down a speaker’s identity, which is useful when validating, for example, whether it’s still the same person behind an online avatar.
Now given a paragraph of text sampled from someone’s speech, can you find the person’s most commonly used word?
输入
Each input file contains one test case. For each case, there is one line of text no more than 1048576 characters in length, terminated by a carriage return ‘\n’. The input contains at least one alphanumerical character, i.e., one character from the set [0-9 A-Z a-z].
输出
For each test case, print in one line the most commonly occurring word in the input text, followed by a space and the number of times it has occurred in the input. If there are more than one such words, print the lexicographically smallest one. The word should be printed in all lower case. Here a “word” is defined as a continuous sequence of alphanumerical characters separated by non-alphanumerical characters or the line beginning/end.
Note that words are case insensitive.
样例输入
Can1: "Can a can can a can? It can!"
样例输出
can 5
思路
这题的大致意思就是让你数一句话(注意,这里的一句话不是双引号里的内容,是整条字符串的内容)里面出现次数最多的单词,数单词的时候不区分大小写,但是输出的时候单词一定得是小写,并且如果有多个单词出现次数相同的话,就输出字典序小的那个。
这题很明显需要建立一种hash映射的关系,试想,假如说我能把一个句子里出现过的所有单词作为下标,其内容作为出现过的次数,那么只要输出内容最大的下标和值就是答案了。之前字符串对应整型的hash我们用的是26进制转10进制的方法,如果忘记了如何写hash函数的话,那就可以用map来做啦,会方便很多(比如这题)~
很显然,我们肯定是要建立string和int的映射关系,那么如何获得每个单词(题目里单词的定义是连续的字母和数字促成的字符串,比如样例的Can1也是一个单词)呢,我参考了柳神的思路,运用了非常方便的C标准库
C 标准库 -
其实如果知道这个库和里面的关键函数用法的话,这题就非常简单,只需要从头开始判断字符串的每一位是否是字符和数字,如果是,那就串接到临时字符串tmp上(string的加等+=操作是允许字符串和字符串接的),一直循环,直到下一位不是字符和数字,那就把它对应在map里的值加1(mp[tmp]++),并且清空临时字符串tmp,用来准备串接下一个单词。
以上操作对于大部分样例都是没问题的,但是呢,Codeup会答案错误83,而PAT也是23分(最后一个样例点过不了)。不过呢,这不是什么太大的问题,细心的我们当然能发现它:
The input contains at least one alphanumerical character, i.e., one character from the set [0-9 A-Z a-z].
看到关键信息了吧:至少一位字母数字的字符。也就是说,有可能最后一个样例是只打了一个数字1或者只打了一个字母a,那么我们上面的判断方法是,直到下一位不是字母和数字,才进行mp[tmp]++,并且清空字符串tmp的操作。这里如果只输入字母数字的话,也就根本不存在下一位不是字母数字的情况了,那么根本就不会在map里建立映射关系。
所以,我们需要对字符串的最后一位还是不是字母字符进行特判,如果依然是字母字符,那么我这次tmp串接好了以后,直接mp[tmp]++,并清空字符串,这样的话,就算结束循环,我们也已经把该放的单词放进map里面了。
另外,在寻找最大值的时候,最好不要用迭代器赋值的语法,我一开始声明了一个迭代器temp,然后每次更新最大值,就把当前的迭代器it赋值给temp,最后只要temp->first和temp->second就能得到最大值对应的key和value了。虽然这种语法对于前几个测试点都没什么问题,但是最后一个测试点会发生段错误,在Codeup上就是运行错误。所以在这里尽量声明两个和key、value相对应的变量来存放最大值对应的key和value,这样就能避免各种越界的问题了。
代码
#include
#include
#include
#include
#include
#include
#include
using namespace std;
map<string, int> mp;
int main(){
string a;
while(getline(cin, a)){//想要用cin读入空格需要用getline(C语言中是gets)
string tmp;
for(int i=0;i<a.length();i++){
if(isalnum(a[i])&&i!=a.length()-1){//如果当前位是字母和数字且不是最后一位
a[i] = tolower(a[i]);//转换为小写
tmp += a[i];//给tmp串上当前字符
continue;//继续下一轮循环
}
if(isalnum(a[i])&&i==a.length()-1){//如果最后一位还是字母和数字
a[i] = tolower(a[i]);
tmp += a[i];
mp[tmp]++;
tmp.clear();
}
if(tmp.length()!=0){//如果tmp不为空(这里是为了防止下一次还是非字母数字时,执行mp[tmp]++的操作)
mp[tmp]++;
tmp.clear();
}
}
int max = 0;
string keymax;
for(map<string, int>::iterator it = mp.begin();it!=mp.end();it++){
if(it->second>max){//如果键的值大,则更新max的值
keymax = it->first;
max = it->second;
}
}
cout<<keymax<<" "<<max<<endl;
mp.clear();
}
return 0;
}
感觉上面这题最大的难度就是想到C的库函数isalnum()的用法,如果不知道这个函数的话,一个一个遍历去找单词还挺麻烦的(而且还要转小写),并且在这里提醒一下我自己:此类运用map的题目,可以声明一个临时字符串tmp来进行串接操作,然后串接完直接放在map里就好了,我一开始想着把所有单词都切割出来,然后再一个一个放进map里,这显然是有点蠢的操作_(:з」∠)_
所以在这里建议大家没事干的时候可以多看看C和C++的标准库,没准就发现了什么非常方便的神仙函数能使用(比如前天的set其实直接就有交并集的封装函数)。
总的来说,map提供给了我们一个很好的用于hash映射的容器,平时遇到字符或者字符串之类对应一个整数的问题,统统可以用map来解决,既节省了空间,又简洁了代码。