题意

给定一个目标单词和一整行文章。匹配时不区分大小写,但必须匹配完整单词,不能只匹配某个长单词的一部分。输出出现次数和第一次出现的位置;如果没有出现,输出 -1。

思路

先把目标单词和文章都转成小写。

为了保证“完整单词”匹配,可以在文章两端各补一个空格,并把目标单词也变成 " " + word + " "。这样只有左右都是边界空格时才会匹配。

如果:

text
padded_article = " " + article + " "
padded_word = " " + word + " "

那么 padded_article.find(padded_word) 返回的位置,刚好等于目标单词在原文章中的起始位置。

这题是整行输入和字符串查找练习,不创建 brute.py。

Python 知识

  • /home/rainboy/mycode/hugo-blog/content/program_language/python/oj_input_output_cheatsheet.md:文章包含空格,需要整行读取,不能用 split() 丢掉空格位置。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/input_output_and_strings.md:lower()、find()、count() 是常用字符串操作。
  • rstrip("\n") 只删除行末换行,保留文章中的空格。
  • find() 找不到返回 -1。

代码

python
word = input().strip().lower()
article = input().rstrip("\n").lower()

padded_article = " " + article + " "
padded_word = " " + word + " "

first_position = padded_article.find(padded_word)

if first_position == -1:
    print(-1)
else:
    # 逐步 find 统计次数:str.count 按不重叠匹配计数,对单字母单词(如 " a a ")会漏数
    count_match = 0
    pos = first_position
    while pos != -1:
        count_match += 1
        pos = padded_article.find(padded_word, pos + 1)
    print(count_match, first_position)

Guide 风格代码

cppbook《C++ 快速入门》教学风格的写法(std:: 前缀、i += 1 循环、0 起始下标):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-08-14 15:18
 * update_at: 2026-08-14 15:18
 */
/* P1308 统计单词数:整行读入,转成小写后按空格切出单词,完整匹配才计数。 */

#include <iostream>
#include <string>

// 大写字母转小写,其他字符原样返回
char to_lower_char(char ch) {
    if (ch >= 'A' && ch <= 'Z') {
        return char(ch - 'A' + 'a');
    }
    return ch;
}

// 判断 article[left..right-1] 这一段与 word 是否完全相同
bool is_same_word(const std::string& article, int left, int right,
                  const std::string& word) {
    if (right - left != (int)word.size()) {
        return false;
    }
    for (int k = 0; k < right - left; k += 1) {
        if (article[left + k] != word[k]) {
            return false;
        }
    }
    return true;
}

int main() {
    std::string word;
    std::string article;
    std::getline(std::cin, word);    // 文章里有空格,必须整行读取
    std::getline(std::cin, article);

    // 两个字符串都转成小写,匹配时不区分大小写
    for (int i = 0; i < (int)word.size(); i += 1) {
        word[i] = to_lower_char(word[i]);
    }
    for (int i = 0; i < (int)article.size(); i += 1) {
        article[i] = to_lower_char(article[i]);
    }

    int match_count = 0;
    int first_pos = -1;  // -1 表示还没有出现过

    // 逐个切出文章中以空格分隔的单词,left..right-1 是当前单词
    int i = 0;
    int length = (int)article.size();
    while (i < length) {
        while (i < length && article[i] == ' ') {
            i += 1;  // 跳过单词前的空格
        }
        if (i >= length) {
            break;  // 后面已经没有单词
        }

        int left = i;  // 单词起始位置,位置从 0 开始
        while (i < length && article[i] != ' ') {
            i += 1;
        }
        int right = i;

        if (is_same_word(article, left, right, word)) {
            match_count += 1;
            if (first_pos == -1) {
                first_pos = left;
            }
        }
    }

    if (match_count == 0) {
        std::cout << -1 << '\n';
    } else {
        std::cout << match_count << ' ' << first_pos << '\n';
    }

    return 0;
}

Pythonic 写法

re.finditer + 单词边界统计出现次数与首位置:

python
import re

word = input().strip()
article = input().rstrip("\n")
matches = list(re.finditer(rf"(?i)\b{re.escape(word)}\b", article))
print(-1 if not matches else f"{len(matches)} {matches[0].start()}")

STL 写法

用 string 的 find 做整词匹配:把目标单词和文章都转成小写,再各补一个空格,然后在补过空格的文章里循环查找 " " + word + " ",每次都用 string::npos 判断是否还要继续找。因为 find 返回的下标指向单词左边那个边界空格,这个下标正好就是单词首字母在原文里的位置,不用再换算。相比上面手写的逐词切分,这里省掉了 left、right 两个下标的维护,代价是额外拼出两个补空格的字符串。

对应的 cppbook 章节:string:把字符串当作可操作的数据

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-07 01:45
 * update_at: 2026-10-07 01:57
 */
// 这是 STL 写法:把目标词和文章都转成小写,再在两端各补一个空格,
// 然后用 string::find 反复查找 " " + word + " ",用 string::npos 判断是否还要继续。
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

string target_word;     // 第一行给出的目标单词(转小写后)
string article;         // 第二行给出的文章(转小写后)
string padded_word;     // " " + target_word + " ",两端补空格,把“单词边界”变成普通子串匹配
string padded_article;  // " " + article + " ",让文章开头和结尾的单词也有边界空格

// 大写字母转小写,其他字符原样返回。
char to_lower_char(char ch) {
    if (ch >= 'A' && ch <= 'Z') {
        return char(ch - 'A' + 'a');
    }
    return ch;
}

// 把字符串 s 里的大写字母全部改成小写。
void to_lower_string(string& s) {
    ll len = s.size();
    for (ll i = 0; i < len; i++) {
        s[i] = to_lower_char(s[i]);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    getline(cin, target_word); // 文章里含空格,两行都必须整行读入
    getline(cin, article);

    to_lower_string(target_word);
    to_lower_string(article);

    padded_word = " " + target_word + " ";
    padded_article = " " + article + " ";

    ll match_count = 0;    // 整词匹配出现的次数
    size_t first_pos = string::npos; // 第一次出现的位置(原文下标);npos 表示还没出现过

    // 补空格后,padded_word 的第一个字符就是单词左边的那个空格,
    // 所以 find 返回的下标正好是单词首字母在原文里的位置(位置从 0 开始)。
    // 每次从 pos + 1 继续找,这样长度为 1 的单词(如 "a" 在 "a a" 里)也能全部数到。
    size_t pos = padded_article.find(padded_word);
    while (pos != string::npos) {
        match_count++;
        if (first_pos == string::npos) {
            first_pos = pos;
        }
        pos = padded_article.find(padded_word, pos + 1);
    }

    if (first_pos == string::npos) {
        cout << -1 << '\n';
    }
    else {
        cout << match_count << ' ' << first_pos << '\n';
    }

    return 0;
}

复杂度

设文章长度为 n,字符串查找和计数都是线性级别,时间复杂度为 O(n)O(n),空间复杂度为 O(n)O(n)。

总结

整词匹配的关键是处理边界。给文章和目标词补空格,可以把“左右是单词边界”的判断转化成普通子串查找。