如何在给定起点和终点的字符串中查找子字符串的出现次数?

How to find number of occurrences of a substring in a string given starting point and ending point?

本文关键字:字符串 查找 起点 终点      更新时间:2023-10-16

给定一个字符串和一个子字符串,以及起点和终点的索引,我希望能够在边界中找到该子字符串的出现次数。例如,给定字符串"ACACTACG",我想找到子字符串"AC"从 3 到 7 的出现次数(如果第一个索引为 1)。上面的示例生成输出 2。从 3 到 7,我们有"ACTAC",其中子字符串"AC"出现了 2 次。我似乎无法用C++编码;

这是 AtCoder 初学者竞赛 122 的问题 C:https://atcoder.jp/contests/abc122/tasks/abc122_c

我实际上设法将其编码出来,但超过了时间限制。我需要一种更简单的方法。

这是我提交的TLE结果:

#include <iostream>
using namespace std;
int main()
{
int N, Q;
string s;
cin >> N >> Q >> s;
for(int i = 0; i < Q; i++)
{
int l, r;
cin >> l >> r;
if(l >= r)
{
cout << 0 << endl;
break;
}
int count = 0;
for(int j = l - 1; j < r; j++)
{
if(s[j] == 'A' && s[j + 1] == 'C' && j != r - 1)
{
count++;
j++;
}
}
cout << count << endl;
}
return 0;
}

在做了一些数学运算后,我设法发现我得到TLE的原因是因为我的代码大约有10 ^10条指令,而2秒的时间限制只能执行大约2 * 10 ^8条指令。

字符串 N 对于所有查询都是相同的,您只是在查找模式AC。这意味着您可以预先计算查找表的答案,并避免为每个查询迭代 N。

查找表将具有自字符串开头以来AC出现的次数。对于ACACTACG来说,这将是

A={0,1,1,2,2,2,3,3}

这很有帮助,因为"x 和 y 之间的 AC 出现次数"与"y 之前的出现次数相同,除了 x 之前的出现次数"。每当您必须回答有关范围的问题时,此类表通常很有用

例如,要回答查询 3,7,您需要计算 A[7]-A[3] = 3-1 = 2。

正如Joni所说,您可以创建像arr 这样的查找Arr。由于要匹配的模式的长度为 2,因此基本思想是:

  • 首先创建一个长度为n+1的数组,所有值都填充0
  • 其次,当您匹配字符串中的AC时,您尝试匹配字符串s中的模式AC因此,当您匹配字符串中的时,请在arr中的C索引处标记1
  • 这样,您将在回答任何查询之前仅迭代数组一次
  • 对于每个查询,您可以在O(1)时间内解决此问题,因为找到的总模式将arr[r] - arr[l-1]
  • 注意,请确保查找部分重叠,如以下代码中所述

希望这有帮助:

// This program is created by Ishpreet Singh
#include <iostream>
#include <string>
using namespace std;
int main()
{
int n, q, l, r;
string s;
cin >> n >> q;
cin >> s;
// The lookUp Arr
int arr[n + 1];
arr[0] = arr[1] = 0;
for (int i = 1; i < n; i++)
{
if (s.at(i - 1) == 'A' && s.at(i) == 'C'){
arr[i + 1] = arr[i] + 1;
}
else{
arr[i + 1] = arr[i];
}
}
while (q--)
{
cin >> l >> r;
int ans = arr[r] - arr[l - 1];
// To Remove partial overlaps of AC, like s[l-1] = 'A' and s[l] = 'C'
if (l > 1 && arr[l] == arr[l - 1] + 1){
ans--;
}
cout << ans << endl;
}
return 0;
}

通过这种方式,您可以在O(1)时间内回答查询,因此代码的整体复杂性将O(Q)这将在 1 秒内通过所有测试用例。