[EOJ] 实训题 P3018 查找单词

本文详细解析了在C++中如何使用string类的find函数进行精确的单词匹配搜索,特别关注于处理大小写不敏感的搜索及避免部分匹配的问题。通过实例演示了如何遍历字符串并调整搜索起点,确保搜索效率的同时,保证了搜索结果的准确性。

[EOJ] 实训题 P3018 查找单词

先把题目地址贴出来:P3018 查找单词

题干

有一个单词 W,输出它在字符串 S 中从左到右第一次出现的位置 IDX(设 S 中的第 1 个字符的位置为 1)。W 只由英文字母组成,S 除英文字母和汉字之外在任何位置(包括头和尾)另有一个或多个连续的空格。

查找单词时,不区分大小写,但要求完全匹配,即单词 W 必须与 S 中的某一独立单词在不区分大小写的情况下完全匹配。W 仅是 S 中某一单词的一部分就不算匹配。

输入格式

第 1 行:一个整数 T (1≤T≤10) 为问题数。

接下来共 2T 行,对应每个问题有 2 行,表示 WS (1≤W长度≤10;1≤S长度≤1000000)。

输出格式

对于每个问题,输出一行问题的编号(0 开始编号,格式:case #0: 等)。

然后对应每个问题在一行中输出 IDX
S 中没有找到 W 时输出 None

样例

Input

3
IN
Find a word within a string in English and in Chinese.
to
    Find a word within a string in English and in Chinese.
In
    Find a word within a string in English and in Chinese.

Output

case #0:
29
case #1:
None
case #2:
33

核心代码

思路:

此题思路上很简单。但实现起来有坑。写出来与大家一起避避雷。

tolower()替换大小写一致 + find函数搜索 + 遍历判断

  1. 多case输入,先整理好case格式,注意由于我们要用 getline来接受整行字符串,所以这里必须在每一组输入T后,用get()清除多余的换行符。

  2. 对于每组输入,第一行为一个单词,第二行为一个任意的字符串,其中可能包含数量不等随机出现的空格。显然,应该定义两个字符串分别接收这两行。我选择都用 getline,单词用 cin直接输入也可。

  3. 正式进入搜索过程。其实显然想到用c++ string类自带的 find函数。这个函数会返回第一次出现某个字符串的下标。但题目要求,搜索到的字符串不能是被包含在一个更长的字符串中的。如:单词w为in,而第二行的待搜索串为int,这样显然find是会返回下标0的。与我们想要的情况不符合。所以要加上额外的判断情况。

  4. 写了一种情况如下图:帮助理解。

case

  • 第一行的in为单词,第二行的in in a int in则是待搜索串。中间的‘-’代表一个或多个空格。
  • 分别考虑1.2.3三种情况,分别对应开头、中间、尾部三种情况。对于2我画了两种分别是匹配成功和匹配失败的情况。对于1.3严格来说也有两种,不过情况类似就不赘述了。
  • 先观察2:显然,如果匹配成功,一定会使得目标单词左侧和右侧均为空格。即: ans[pos + len] == ' ' && ans[pos - 1] == ' '。其中pos代表find函数返回的下标,len是单词的长度。ans代表待搜索的字符串。画个图很容易看出这是成立的。
  • 再观察1和3:以1为例,如果仍然用在中间匹配的规则去判断,很容易发现pos - 1访问到了下标为-1的位置,这显然是错误也是不安全的,以EOJ的习惯就是直接报RTE(runtime error)。所以这里需要加一条来判断开头: pos == 0。同样对于3的尾部也要加一条 pos + len == ans.size() 整合一下,综合的判断条件为:if(( pos + len == ans.size() || ans[pos + len] == ' ' ) && ( pos == 0 || ans[pos - 1] == ' ' ))
  • 此外还有一个要解决的问题是,匹配到一个字符串,如果是被包含的匹配,那么势必要继续向后查找,所以我们外圈要套一个循环,利用find()函数的可以指定开始查找位置,每次更新起始查找位置。直到匹配成功就返回下标,或者匹配失败&字符串遍历结束。
  • 思路捋清楚以后就是具体的实现问题。下面就是我要说的坑了!!
  1. 首先给出源码:
完整AC代码如下:
#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
void solve()/* Define function solve() to process one case of the problem    */
{
    string ss = "", ans = "";
    getline(cin, ss);
    getline(cin, ans);
    for(auto &i : ss) // 先分别转小写
        i = tolower(i);
    for(auto &i : ans)
        i = tolower(i);
    for(int i = 0; ;) // 对被查找的字符串进行遍历,每次从位置i开始找
    {
        unsigned int pos = 0;
        int len = ss.size();
        if(ans.find(ss, i) == string::npos || i == ans.size()){
            cout << "None" << endl;
            break;
        }
        else{
            pos = ans.find(ss, i);
            if(( pos + len == ans.size()  ||  ans[pos + len] == ' ' )
                    && ( pos == 0  || ans[pos - 1] == ' ' )){
                cout << pos + 1 << endl; // 输出位置即可 题目要求从1开始输
                break;
            }
            i = pos + len; // 重新定位查找位置
        }
    }
    return ;
}
/******************************************************************************/
/*  DON'T MODIFY main() function anyway!                                      */
/******************************************************************************/
int main()
{
    int T, i;
    (cin >> T).get();
    for(i = 0; i < T; ++i)
    {
        cout << "case #" << i << ":\n";
        solve();
    }
    return 0;
}


  1. 坑就在于 if(( pos + len == ans.size() || ans[pos + len] == ' ' ) && ( pos == 0 || ans[pos - 1] == ' ' ))

    这个地方千万不能把 || 连接的表达式换位,即写成 :

    if((ans[pos + len] == ' ' || pos + len == ans.size()) 
                   && (ans[pos - 1] == ' ' || pos == 0)) 
    

    这里看似和上面的表达并无二样,其实是非常有风险的。由于||的特性,它先判断前面的表达式,那么很容易导致访问到内存非法区域。而如果采用第一种表达方式,当pos + len或pos-1是非法位置时,它们已经先被判断出了非法,也不就不会实际被访问了。

    这是我编程时候的不好的习惯,随手写出来,在EOJ中只会报一个RTE,也很难看出来问题。最终试了很久发现了问题。引以为戒!

### EOJ2654 目解析与解法建议 EOJ2654 是华东师范大学在线评测系统(EOJ)中的道编程目。根据EOJ系统的常见型和风格,这类目通常涉及基础的数据结构操作、算法实现或数学建模。虽然没有直接提及EOJ2654的具体目内容,但可以从EOJ系统的典型目特征出发,结合常见的编程类型,给出可能的解思路与实现方法。 #### 目类型推测 EOJ系统中常见的目包括但限于: - 数值计算与数学建模 - 字符串处理与模式匹配 - 数据结构操作(如栈、队列、链表等) - 图论与搜索算法 - 动态规划与贪心策略 根据目编号(2654)以及EOJ的出规律,该可能属于**中等难度**,可能涉及**数学建模**或**字符串处理**。 #### 示例解法思路(假设目为字符串处理) 假设目要求判断某个字符串是否为回文串(Palindrome),可以采用如下C++实现: ```cpp #include <iostream> #include <string> #include <algorithm> using namespace std; int main() { string s; cin >> s; string rev = s; reverse(rev.begin(), rev.end()); if (s == rev) { cout << "Yes" << endl; } else { cout << "No" << endl; } return 0; } ``` #### 示例解法思路(假设目为数学建模) 若目要求求解某个数列的第n项,例如斐波那契数列,可以使用递归或迭代方法。以下是迭代方法的实现: ```cpp #include <iostream> using namespace std; long long fib(int n) { long long a = 0, b = 1, c; if (n == 0) return a; for (int i = 2; i <= n; i++) { c = a + b; a = b; b = c; } return b; } int main() { int n; cin >> n; cout << fib(n) << endl; return 0; } ``` #### 注意事项 在EOJ系统中,需要注意以下几点以避免运行时错误或超时: 1. **输入输出效率**:对于大规模输入数据,建议使用`scanf`和`printf`代替`cin`和`cout`以提高效率[^1]。 2. **数据类型选择**:在涉及大数运算时,应使用`long long`等大整数类型以防止溢出[^4]。 3. **边界条件处理**:特别注意输入数据的边界情况,如最小值、最大值等。 4. **代码简洁性与可读性**:保持代码结构清晰,避免必要的复杂逻辑,以便调试与优化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值