原题链接

🟠 题目大意
给定一串字符串,要求找出字符串中出现次数最多的单词。
输入
输入一行字符串,字符串长度不超过1048576,所有单词由0
-9
、a
-z
、A
-Z
组成。单词大小写不区分。
输出
输出出现次数最多的单词并打印次数,如果出现了多个单词次数相同的情况,打印字典序较小的那个单词。
🟢 思路分析
首先,题目输入一整行带空格的字符串,因此这里需要使用getline
去读取。
✔️getline
可以读取一整行字符串(包括空格)
string s;
getline(cin, s);
❔那么问题来了,要如何统计各个单词出现的次数呢?、
✔️很简单,使用map
映射一下就好啦。这里我用string
映射int
,即map<string,int>mp
来记录当前单词出现的次数。
map<string,int>mp;
✔️随后使用vector
数组保存所有出现过的单词,方便后续遍历。使用临时string
类型字符串用于拼接单词
vector<string>str;
string ss="";
- 分析到这里,下面的代码就是顺水推舟了
(1)遍历读入的字符串,如果当前字符是数字或字母,就推入临时字符串,否则判断当前单词在这之前是否出现,如果是第一次出现就push进vector数组,随后令该单词出现次数+1即可
❗️注意当索引走到字符串最后一位时无论是否符合条件也要记录该单词。
//遍历读入的字符串
for (int i = 0; i < s.size(); i++)
{
//如果是数字或字母
if (isalnum(s[i]))
{
if (s[i] >= 'A' && s[i] <= 'Z') s[i] += 32;//注意:题目说字母一律按小写输出,因此大写字母要转成小写字母
ss += s[i];
}
//如果不是数字或字母,推入临时字符串的单词,最后一位特判
if(!isalnum(s[i])||i==s.size()-1)
{
if (!mp[ss]) str.push_back(ss);
mp[ss]++;
ss.clear();
}
}
(2)遍历vector数组,找出出现次数最多的单词,保存单词和对应的次数并打印
string ans = "";//保存出现次数最多的单词
int maxans = 0;//保存最多出现次数
for (int i = 0; i < str.size(); i++)
{
if (mp[str[i]] > maxans)
{
maxans = mp[str[i]];
ans = str[i];
}
}
//按格式打印
cout << ans << ' ' << maxans;
- 到这里,代码就写好了,我们来测试一下
🔴 输入题目的测试样例
Can1: "Can a can can a can? It can!"
- 可以发现和预期结果有所区别,似乎输出了一个空字符串,这是为什么呢?
☑️经过调试可以发现,当临时字符串为空时也被记录为单词了
✔️ 因此只需要给临时字符串加个判空即可
if(!isalnum(s[i])||i==s.size()-1)
{
if (ss.length() != 0)//判空:如果临时字符串长度不为0
{
if (!mp[ss]) str.push_back(ss);
mp[ss]++;
ss.clear();
}
}
完整代码及测试结果
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
string s;
map<string, int>mp;
vector<string>str;
int main()
{
getline(cin, s);
string ss = "";
for (int i = 0; i < s.size(); i++)
{
if (isalnum(s[i]))
{
if (s[i] >= 'A' && s[i] <= 'Z') s[i] += 32;
ss += s[i];
}
if(!isalnum(s[i])||i==s.size()-1)
{
if (ss.length() != 0)
{
if (!mp[ss]) str.push_back(ss);
mp[ss]++;
ss.clear();
}
}
}
string ans = "";
int maxans = 0;
for (int i = 0; i < str.size(); i++)
{
if (mp[str[i]] > maxans)
{
maxans = mp[str[i]];
ans = str[i];
}
}
cout << ans << ' ' << maxans;
return 0;
}
🔵 代码改进
- 通过这道题后,通过观察题解,发现这道题目无需使用中间变量保存答案。
✅即通过遍历map容器直接获取对应单词和出现次数。
下面提供两种方式改进。
(1)范围for遍历map容器
string ans = "";
int maxans = 0;
//使用范围for遍历map容器
for (auto& elem : mp)
{
if (elem.second > maxans)
{
maxans = elem.second;
ans = elem.first;
}
}
(2)迭代器遍历map容器
it->first
是map的键,it->second
是对应键的值。
string ans = "";
int maxans = 0;
//使用迭代器遍历
//不使用自动类型推导auto
/*map<string, int>::iterator it;
for (it = mp.begin(); it != mp.end(); it++)*/
//使用自动类型推导auto
for (auto it = mp.begin(); it != mp.end(); it++)
{
if (it->second > maxans)
{
maxans = it->second;
ans = it->first;
}
}
- 观察输出要求可以发现,如果存在多个答案则输出字典序最小的那个单词
ps:可能pta平台没有出现这类情况的样例,导致没考虑这个情况也能AC。
✔️因此我们应该加上这个判断才对
for (auto& elem : mp)
{
if (elem.second > maxans || elem.second == maxans && elem.first < ans)
{
maxans = elem.second;
ans = elem.first;
}
}
🟤 总结提炼
- 首先,这道题目看似很简单,事实上有很多细节(陷阱)。例如遍历输入的字符串最后一位需要特判和空字符串问题,这两个点花了我不少时间才解决。除非够细心,否则只能通过调试花时间去找问题。
✅多做题,提高做题敏感度和debug能力。 - 通过对比各路大佬的题解,我的代码还是过于臃肿了,使用了很多不必要的变量,让代码看着很乱,思路很混乱。
❔至于为什么会出现这种问题,本质上还是我对stl的应用不够熟练,在这之前我甚至认为map是无法遍历的。
✅多多使用stl去解题,了解stl各种容器的特性,遍历方式的相同点和不同点。