1. 题目解析与解题思路
这道题目要求我们在一段给定的文本中统计特定单词出现的次数,并记录该单词第一次出现的位置。题目看似简单,但实际处理时需要特别注意几个关键点:
- 大小写不敏感:题目要求忽略大小写差异
- 单词边界判断:需要确保匹配的是完整单词而非子串
- 位置记录:需要准确记录第一次出现的位置
1.1 核心算法选择
使用C++标准库中的字符串处理功能是最直接的选择。主要用到以下STL组件:
string类:存储和处理字符串tolower函数:统一转换为小写find方法:查找子串位置- 边界检查:确保匹配的是完整单词
这种方案的时间复杂度为O(n),其中n是文本长度,对于题目给定的数据规模完全够用。
1.2 边界条件处理
需要特别注意以下几种边界情况:
- 单词出现在文本开头
- 单词出现在文本结尾
- 文本中不存在目标单词
- 目标单词前后有标点符号而非空格
2. 代码实现详解
2.1 预处理函数
cpp复制void toLower(string& str) {
for(char& c:str) {
c = tolower(c);
}
}
这个辅助函数将字符串统一转换为小写,确保大小写不敏感的比较。使用引用传递避免拷贝,直接修改原字符串。
注意:这里使用范围for循环遍历字符串,比传统索引访问更简洁安全。char&确保直接修改原字符串字符。
2.2 主函数结构
cpp复制int main() {
string sub; // 目标单词
cin >> sub;
toLower(sub);
string str; // 输入文本
cin.ignore();
getline(cin, str);
toLower(str);
// 查找和统计逻辑
// ...
return 0;
}
输入处理分为两部分:
- 读取目标单词(使用cin直接读取)
- 读取整行文本(使用getline,之前需要ignore跳过换行符)
2.3 核心查找逻辑
cpp复制size_t pos = 0;
int cnt = 0;
int first = -1;
while((pos = str.find(sub, pos)) != string::npos) {
int len = sub.size();
if(pos == 0) { // 出现在开头的情况
if(str[pos+len]==' ') {
cnt++;
if(first==-1) first = pos;
}
} else { // 一般情况
if(str[pos-1]==' '&&str[pos+len]==' ') {
cnt++;
if(first==-1) first = pos;
}
}
pos += len;
}
这段代码实现了:
- 循环查找所有出现位置
- 检查单词边界(前后为空格或字符串边界)
- 统计出现次数和记录首次位置
技巧:pos += len的移动方式比pos++更高效,跳过已匹配部分
2.4 输出处理
cpp复制if(cnt!=0) {
cout<<cnt<<" "<<first<<endl;
} else {
cout<<-1<<endl;
}
根据题目要求输出结果:
- 找到时输出次数和首次位置
- 未找到时输出-1
3. 关键问题与优化
3.1 单词边界判断的陷阱
原始代码的边界判断存在潜在问题:
- 当匹配出现在字符串末尾时,str[pos+len]会越界访问
- 没有考虑标点符号等非空格分隔符的情况
改进版本:
cpp复制bool isWordBoundary(const string& s, size_t pos) {
return pos == 0 || isspace(s[pos-1]);
}
bool isWordEnd(const string& s, size_t pos, size_t len) {
return pos + len == s.size() || isspace(s[pos+len]);
}
// 在循环中使用
if(isWordBoundary(str, pos) && isWordEnd(str, pos, sub.size())) {
cnt++;
if(first == -1) first = pos;
}
3.2 性能优化建议
- 使用KMP算法:对于极端情况(如大量重复模式)更高效
- 并行处理:对于超长文本可以考虑分割并行处理
- 内存优化:对于超大文本可以使用流式处理
4. 完整改进代码
cpp复制#include<iostream>
#include<string>
#include<cctype>
using namespace std;
void toLower(string& str) {
for(char& c:str) {
c = tolower(c);
}
}
bool isWordBoundary(const string& s, size_t pos) {
return pos == 0 || isspace(s[pos-1]);
}
bool isWordEnd(const string& s, size_t pos, size_t len) {
return pos + len == s.size() || isspace(s[pos+len]);
}
int main() {
string sub;
cin >> sub;
toLower(sub);
string str;
cin.ignore();
getline(cin, str);
toLower(str);
size_t pos = 0;
int cnt = 0;
int first = -1;
size_t len = sub.size();
while((pos = str.find(sub, pos)) != string::npos) {
if(isWordBoundary(str, pos) && isWordEnd(str, pos, len)) {
cnt++;
if(first == -1) first = pos;
}
pos += len;
}
if(cnt != 0) {
cout << cnt << " " << first << endl;
} else {
cout << -1 << endl;
}
return 0;
}
5. 测试用例设计
5.1 常规测试用例
输入:
code复制hello
Hello world hello everyone
输出:
code复制2 0
5.2 边界测试用例
- 目标单词在开头:
code复制apple
apple banana apple
输出:
code复制2 0
- 目标单词在结尾:
code复制world
hello world
输出:
code复制1 6
- 不存在的单词:
code复制test
hello world
输出:
code复制-1
5.3 特殊字符测试
输入:
code复制word
This word, has word! and word.
输出:
code复制3 5
6. 常见错误与调试
6.1 段错误问题
常见原因:
- 未处理字符串末尾情况,导致越界访问
- 空字符串输入未处理
解决方法:
- 添加边界检查函数
- 对空输入进行特殊处理
6.2 计数错误问题
常见原因:
- 单词边界判断不完整
- 大小写转换不彻底
解决方法:
- 使用isspace()代替' '比较
- 确保所有输入都经过toLower处理
6.3 性能问题
当文本非常大时(如MB级别),可以考虑以下优化:
- 分块处理文本
- 使用更高效的字符串搜索算法
- 避免不必要的字符串拷贝
7. 扩展思考
7.1 多单词统计
如果需要统计多个单词的出现次数,可以:
- 使用map<string, int>存储统计结果
- 对每个单词执行类似查找过程
- 时间复杂度为O(n*m),n为文本长度,m为单词数
7.2 正则表达式方案
使用C++11的regex库可以实现更灵活的匹配:
cpp复制regex word_regex("\\b" + sub + "\\b", regex_constants::icase);
auto words_begin = sregex_iterator(str.begin(), str.end(), word_regex);
auto words_end = sregex_iterator();
int count = distance(words_begin, words_end);
这种方法代码更简洁,但性能可能略低。
7.3 跨语言实现
同样的算法可以应用于其他语言,如Python:
python复制import re
def count_words(sub, text):
pattern = fr'\b{sub}\b'
matches = re.finditer(pattern, text, re.IGNORECASE)
matches = list(matches)
if not matches:
return -1
return len(matches), matches[0].start()
这种实现更简洁,但隐藏了底层细节。