1. 题目背景与需求解析
这道来自NOIP 2011普及组的编程题,考察的是字符串处理的基础能力。题目要求实现一个类似文本编辑器查找功能的小程序,核心需求可以拆解为三个关键点:
- 大小写不敏感匹配:比如搜索单词"Apple"时,文章中出现的"apple"、"APPLE"都应该被识别为匹配项
- 完全匹配原则:需要匹配独立的单词,而不是子串。例如搜索"an"时,"banana"中的"an"不算匹配
- 位置统计要求:需要记录单词首次出现的位置(从0开始计数),以及总出现次数
特别注意:题目中的"位置"指的是单词首字母在整个文章字符串中的索引值,空格也计入位置统计。例如在字符串"hello world"中,"world"的首字母位置是6(h=0, e=1, l=2, l=3, o=4, 空格=5, w=6)
2. 核心算法设计思路
2.1 输入处理与预处理
首先需要正确处理输入数据:
python复制# 读取输入
target_word = input().strip() # 第一行:目标单词
article = input().strip() # 第二行:文章内容
关键预处理步骤:
- 统一转换为小写(实现大小写不敏感)
- 使用split()方法分割文章为单词列表(自动处理连续空格情况)
- 保留原始文章字符串用于位置计算
python复制target_lower = target_word.lower()
words = article.split() # 默认按任意空白字符分割
words_lower = [w.lower() for w in words]
2.2 匹配算法实现
最直接的实现方式是遍历检查每个单词:
python复制count = 0
first_pos = -1
for i in range(len(words_lower)):
if words_lower[i] == target_lower:
if count == 0: # 首次匹配
# 计算原始位置
first_pos = article.lower().find(" " + target_lower + " ")
if first_pos == -1: # 可能在开头
if article.lower().startswith(target_lower + " "):
first_pos = 0
count += 1
但这种方法在极端情况下(如文章很长)可能效率不高,更优的方案是:
- 在原始字符串中搜索(保留空格信息)
- 使用正则表达式确保完全匹配
python复制import re
pattern = r'\b' + re.escape(target_word) + r'\b'
matches = re.finditer(pattern, article, flags=re.IGNORECASE)
2.3 位置计算优化
直接使用字符串查找可能遇到边界情况:
- 单词出现在开头
- 单词出现在结尾
- 连续多个空格
更可靠的位置计算方式:
python复制def find_first_position(article, target):
# 构造正则模式,确保单词边界匹配
pattern = re.compile(rf'\b{re.escape(target)}\b', re.IGNORECASE)
match = pattern.search(article)
return match.start() if match else -1
3. 完整实现与边界处理
3.1 Python参考代码
python复制import re
def count_word_occurrences():
target = input().strip()
text = input().strip()
if not target or not text:
print(-1)
return
# 构造匹配模式(单词边界+忽略大小写)
pattern = re.compile(rf'\b{re.escape(target)}\b', re.IGNORECASE)
matches = list(pattern.finditer(text))
if not matches:
print(-1)
else:
first_pos = matches[0].start()
count = len(matches)
print(count, first_pos)
count_word_occurrences()
3.2 关键边界情况测试
需要特别测试的边界场景:
- 目标单词在文章开头/结尾
- 文章包含连续多个空格
- 目标单词包含特殊字符(虽然题目说明只有字母)
- 空输入情况
- 大小写混合情况
测试用例示例:
code复制# 样例1
输入:
To
to be or not to be is a question
输出:
2 0
# 样例2
输入:
to
Did you go to the museum
输出:
1 11
# 边界测试
输入:
a
a b c a a
输出:
3 0
4. 性能优化与算法分析
4.1 时间复杂度分析
- 原始实现:O(n*m),其中n是文章长度,m是目标单词长度
- 正则表达式:通常O(n),但具体取决于正则引擎实现
- KMP算法:可以优化到O(n+m)但实现复杂
对于题目给出的约束条件(文章长度≤1e6),Python的正则表达式实现已经足够,在洛谷的评测系统中能AC通过。
4.2 空间优化技巧
- 避免存储整个小写字符串,可以边处理边比较
- 对于超大文本,可以考虑流式处理(虽然本题不需要)
- 使用生成器而非列表保存匹配结果
优化后的内存高效版本:
python复制def count_words_efficient():
target = input().strip().lower()
text = input().strip()
count = 0
first_pos = -1
# 使用生成器逐单词处理
for match in re.finditer(r'\b([a-zA-Z]+)\b', text):
word = match.group(1)
if word.lower() == target:
if count == 0:
first_pos = match.start()
count += 1
print(f"{count} {first_pos}" if count > 0 else -1)
5. 常见错误与调试技巧
5.1 典型错误类型
-
边界匹配错误:
- 忘记处理单词在开头/结尾的情况
- 连续空格导致split()结果异常
-
位置计算错误:
- 错误计算空格位置
- 忘记索引从0开始
-
性能问题:
- 使用双重循环导致超时
- 不必要的字符串拷贝
5.2 调试建议
- 打印中间变量:
python复制print(f"Target: {target}, Text: {text}")
print(f"Words list: {words}")
- 使用断言检查预处理结果:
python复制assert ' ' not in target, "Target word should not contain spaces"
- 构造极端测试用例:
python复制# 最小输入测试
assert count_words("a", "a") == (1, 0)
# 超大输入测试
long_text = "a " * 1000000
assert count_words("a", long_text) == (1000000, 0)
6. 算法扩展与变种思考
6.1 多单词统计
如果需要统计多个单词的出现频率,可以优化为:
python复制from collections import defaultdict
def count_multiple_words(words_to_count, text):
word_counts = defaultdict(int)
for word in re.findall(r'\b([a-zA-Z]+)\b', text):
lower_word = word.lower()
if lower_word in words_to_count:
word_counts[lower_word] += 1
return word_counts
6.2 模糊匹配支持
如果需要支持模糊匹配(如允许1个字符差异),可以使用编辑距离算法:
python复制def is_similar(word1, word2, max_diff=1):
if abs(len(word1) - len(word2)) > max_diff:
return False
# 实现编辑距离计算
# ...
6.3 跨语言实现对比
C++版本的核心实现(性能更高):
cpp复制#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string target, text;
getline(cin, target);
getline(cin, text);
transform(target.begin(), target.end(), target.begin(), ::tolower);
int count = 0, firstPos = -1;
size_t pos = 0;
while (pos < text.length()) {
// 跳过空格
while (pos < text.length() && text[pos] == ' ') pos++;
if (pos >= text.length()) break;
// 提取单词
size_t end = pos;
while (end < text.length() && text[end] != ' ') end++;
string word = text.substr(pos, end - pos);
transform(word.begin(), word.end(), word.begin(), ::tolower);
if (word == target) {
if (count == 0) firstPos = pos;
count++;
}
pos = end;
}
if (count > 0) cout << count << " " << firstPos;
else cout << -1;
return 0;
}
在实际编程竞赛中,掌握这种基础字符串处理能力至关重要。这道题虽然表面简单,但考察了对字符串操作的全面理解,包括输入处理、边界情况考虑、算法效率等多个方面。建议初学者通过这道题深入理解字符串处理的常见模式和陷阱。
