题解:洛谷 P1308 统计单词数

【题目来源】

洛谷:P1308 [NOIP 2011 普及组] 统计单词数 - 洛谷

【题目描述】

一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数。

现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中出现的次数和第一次出现的位置。注意:匹配单词时,不区分大小写,但要求完全匹配,即给定单词必须与文章中的某一独立单词在不区分大小写的情况下完全相同(参见样例 1),如果给定单词仅是文章中某一单词的一部分则不算匹配(参见样例 2)。

【输入】

\(2\) 行。

\(1\) 行为一个字符串,其中只含字母,表示给定单词;

\(2\) 行为一个字符串,其中只可能包含字母和空格,表示给定的文章。

【输出】

一行,如果在文章中找到给定单词则输出两个整数,两个整数之间用一个空格隔开,分别是单词在文章中出现的次数和第一次出现的位置(即在文章中第一次出现时,单词首字母在文章中的位置,位置从 \(0\) 开始);如果单词在文章中没有出现,则直接输出一个整数 \(−1\)

【输入样例1】

To
to be or not to be is a question

【输出样例1】

2 0

【核心思想】

  1. 问题分析:给定一个单词和一个文章(可能包含空格),要求统计该单词在文章中作为独立单词出现的次数和第一次出现的位置(位置从 0 开始)。匹配时不区分大小写,但必须完全匹配,即单词不能是其他单词的一部分。这是一个字符串匹配与分词问题,核心在于确保匹配的是完整的单词。

  2. 算法选择

    • 统一大小写 + 添加分隔符:先将单词和文章都转换为小写(或大写)。然后在单词和文章的首尾各添加一个空格,这样在文章中查找 " " + 单词 + " " 即可确保匹配的是独立单词(因为空格是单词分隔符)。
    • 使用 find 循环查找:利用 string::find 在文章中反复查找目标子串,并记录位置和次数。
  3. 关键步骤

    • 读入:使用 getline 分别读入单词 str 和文章 sent(因为文章可能包含空格)。
    • 大小写转换:遍历两个字符串,将大写字母转换为小写字母。
    • 添加分隔符str = " " + str + " "sent = " " + sent + " "
    • 查找与统计
      • 使用 pos = sent.find(str, 0) 查找第一次出现位置。
      • pos != string::npos,则 ans = poscounts++,然后继续从 pos+1 开始查找,直到找不到为止。
    • 输出:若 ans == -1(即未找到),输出 -1;否则输出 countsans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(L + K \cdot n)\),其中 \(L\) 是文章长度,\(n\) 是单词长度,\(K\) 是匹配次数(最坏 \(O(L)\))。但 find 通常高效。
    • 空间复杂度:\(O(L + n)\),用于存储转换后的字符串。
  5. 字符串匹配与分隔

    • 独立单词匹配:通过在单词前后添加空格,确保匹配的是完整单词,而不是其他单词的一部分(如 "to" 不会匹配 "tonight")。
    • 大小写不敏感:统一转换为小写后再比较,是最常用的方法。
    • find 循环string::find 返回第一次出现的位置,若未找到返回 string::npos(在 C++ 中通常为 -1)。通过循环查找并更新起始位置,可以统计所有出现。
    • 位置定义:题目要求输出单词首字母在文章中的位置(从 0 开始)。由于我们在文章首部添加了一个空格,实际位置需要减 1?但代码中直接输出了 ans(即添加空格后的位置),而样例输出为 0("to" 在文章开头),这是因为添加的空格使得 " to " 匹配的位置为 0,恰好符合要求(文章原始开头前有一个虚拟空格,但输出时位置仍从 0 开始)。因此,直接输出 ans 是正确的。
    • 适用场景:这类“统计独立单词出现次数”是文本处理的常见需求,也是 string 操作的综合练习。

【算法标签】

普及- #字符串入门

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int main()
{
    string str, sent; // 声明两个字符串变量,str用于存储要查找的子串,sent用于存储目标字符串
    int pos, counts=0, ans; // pos用于存储查找位置,counts用于计数匹配次数,ans用于存储首次匹配的位置

    getline(cin, str); // 从标准输入读取一行字符串到str
    getline(cin, sent); // 从标准输入读取一行字符串到sent

    // 大小写转换:将str和sent中的所有大写字母转换为小写字母
    for (int i=0; i<str.length(); i++) {
        if (str[i]>='A' && str[i]<='Z') { // 判断字符是否为大写字母
            str[i] = str[i] - 'A' + 'a'; // 将大写字母转换为小写字母
        }
    }
    for (int i=0; i<sent.length(); i++) {
        if (sent[i]>='A' && sent[i]<='Z') { // 判断字符是否为大写字母
            sent[i] = sent[i] - 'A' + 'a'; // 将大写字母转换为小写字母
        }
    }

    str = " " + str + " "; // 在str前后各添加一个空格,防止部分匹配
    sent = " " + sent + " "; // 在sent前后各添加一个空格,防止部分匹配

    // 完全匹配:查找str在sent中的所有出现位置
    pos = sent.find(str, 0); // 从sent的第0个位置开始查找str
    ans = pos; // 记录首次匹配的位置
    while (pos != -1) { // 当找到匹配时继续查找
        counts++; // 匹配次数加1
        pos = sent.find(str, pos+1); // 从上次匹配位置的下一个位置继续查找
    }

    // 输出结果:如果未找到匹配,输出-1;否则输出匹配次数和首次匹配的位置
    if (ans == -1) {
        cout << -1;
    } else {
        cout << counts << " " << ans;
    }

    return 0; // 主函数返回0,表示程序正常结束
}

【运行结果】

To
to be or not to be is a question
2 0
posted @ 2026-08-30 09:17  团爸讲算法  阅读(17)  评论(0)    收藏  举报