文章目录
- 题目
- 题目描述
- 输入输出格式
- 数据范围
- 测试样例
- 思路
- 代码
- 复杂度分析
- 时间复杂度
- 空间复杂度
题目
题目链接🔗
题目描述
在《哈利波特》的魔法世界里,赫敏发现了一本古老的魔法书。这本书记录了许多强力的魔咒,但每个魔咒都被特定的起始咒语 p 1 p_1 p1 和结束咒语 p 2 p_2 p2 包裹着。她需要找到这些咒语,才能揭示出真正的魔法奥秘。
请你帮助赫敏从字符串 s s s 中找到所有被 p 1 p_1 p1 和 p 2 p_2 p2 包裹的魔咒,以便她能解开魔法书的奥秘,以下是赫敏发现的一些规律。
-
起始咒语 p 1 p_1 p1 和结束咒语 p 2 p_2 p2 中可能会有魔法师可以自由发挥的地方使用字符
#
表示,这些地方可以是 0 ∼ 9 0 \sim 9 0∼9 的任意数字。 -
任何一个有效的魔咒以及它的起始咒语和结束咒语不会与其他有效的魔咒重叠。
-
空白的魔咒也是魔咒。
-
若有魔咒先前出现过,该魔咒仍然需要输出。
-
我们按照从左到右的顺序进行处理。每当我们遇到一个起始咒语,就开始寻找对应的结束咒语,直到找到为止,形成一个有效的魔咒,完成了一次匹配。在整个过程中,已经匹配到的魔咒(包括起始咒语和结束咒语)不再参与后续的匹配。
输入输出格式
【输入格式】
第一行给出一个数字 T T T ,表示样例数。
对于每一个样例,第一行给出 p 1 p_1 p1。
第二行给出 p 2 p_2 p2。
第三行给出 s s s。
【输出格式】
对于每一个样例,第一行输出一个数字 n n n ,表示找到的魔咒总数。
接下来 n n n 行输出找到的魔咒。
数据范围
0 < T ≤ 50 0<T\le50 0<T≤50
0 < p 1 , p 2 ≤ 10 0<p_1,p_2\le10 0<p1,p2≤10 (此处指字符串长度,下 s s s 同)
0 < s < 10000 0<s<10000 0<s<10000
测试样例
input1
2
ab#c
de
ab0cab1cABCdedeabbcDEFdeab2cdeab3cGHIde
aa
aa
aaAAaaAAaaAAaaAA
output1
3
ab1cABC
GHI
2
AA
AA
思路
题目大意
遍历字符串交替查找p1,p2,记录所有p1,p2之间的子字符串
这道题是基于正则表达式改编的,将p1,p2中的#替换为[0-9],部分选手可能考虑使用 f"{p1}(.*?){p2}"匹配。
但这会导致超时,我们需要直接遍历整个字符串进行逐字符匹配:
- 对于不是 # 的字符,要求两个字符串中的对应字符一致。
- 对于 #字符,要求匹配的字符的 ASCI 码值在区间[48,57]之中(判断是否是数字)。由于 p1,p2 的长度非常短,可以视为只对 s 进行了 O(n)的一次遍历,时间复杂度为 O(n)。
代码
#include<bits/stdc++.h>
using namespace std;
// p1, p2 表示起始和结束咒语,s 表示给定的字符串
string p1,p2,s;
// 判断 x 和 y 是否匹配
bool check(string x, string y)
{
// 如果长度不一样,直接返回 false
if(x.size()!=y.size())
return false;
// 遍历每个字符,进行匹配
for(int i=0; i<x.size(); i++)
{
// 如果 y 的字符是 #,检查 x 中相应字符是否是数字
if(y[i]=='#')
{
if(isdigit(x[i])==false) // 如果不是数字,返回 false
return false;
}
else
// 如果 y 中对应位置的字符不是 #,则直接进行字符匹配
if(x[i]!=y[i])
return false;
}
// 如果所有字符都匹配,返回 true
return true;
}
int main()
{
int T;
cin>>T;
while(T--)
{
// 读取 p1, p2 和字符串 s
cin>>p1>>p2>>s;
vector<string> ans; // 用来存储找到的魔咒
// 从字符串 s 开始遍历
for(int i=0; i<s.size(); i++)
{
// 如果当前位置的子串匹配起始咒语 p1
if(check(s.substr(i, p1.size()), p1))
{
int j;
bool found=false;
// 从 i + p1.size() 开始查找匹配的结束咒语 p2
for(j=i+p1.size(); j<s.size(); j++)
{
// 如果找到匹配的结束咒语 p2
if(check(s.substr(j, p2.size()), p2))
{
found=true;
break; // 找到结束咒语后,跳出内层循环
}
}
// 如果找到了匹配的 p2,存储该魔咒,并更新 i 的值跳过已经匹配的部分
if(found)
{
ans.push_back(s.substr(i+p1.size(), j-i-p1.size())); // 存储魔咒
i=j+p2.size()-1; // 更新 i,跳过结束咒语部分
}
else
break; // 如果没有找到结束咒语,结束当前查找
}
}
// 输出找到的魔咒数量
cout<<ans.size()<<endl;
// 输出每个魔咒
for(auto &s: ans)
cout<<s<<endl;
}
return 0;
}
复杂度分析
时间复杂度
对于每个测试用例,我们遍历字符串 s 中的每个字符。每次找到一个起始咒语 p1,我们再从当前位置开始查找结束咒语 p2。因此:
外层循环遍历字符串 s 的每个字符:时间复杂度为 O(n),其中 n 是字符串 s 的长度。
内层循环在找到一个起始咒语后,继续从当前位置查找结束咒语 p2。长度较短,可视为O(1)
因此,整个算法的时间复杂度是 O(n),其中 n 是字符串 s 的长度。
空间复杂度
程序中使用了一个 vector ans 来存储找到的魔咒。最坏情况下,每个字符都能找到一个魔咒,因此空间复杂度为 O(n),其中 n 是字符串 s 的长度。
此外,check 函数中使用了固定大小的字符串,空间复杂度是常数级别的。