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

Posted

技术标签:

【中文标题】如何在给定起点和终点的字符串中查找子字符串的出现次数?【英文标题】:How to find number of occurrences of a substring in a string given starting point and ending point? 【发布时间】:2019-03-26 11:22:46 【问题描述】:

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

这是 AtCoder Beginner Contest 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 条指令。

【问题讨论】:

如果您 100% 确定代码可以正常工作,那么您似乎想要的是应该发布在 codereview.stackexchange.com 上的代码审查。但请先确保on-topic 在那里。 请给你的变量起有意义的名字,这样更容易阅读和理解你的代码,同时也降低了难​​以检测到错误的风险,例如由于 lI1 看起来相似等原因导致的复制粘贴错误、拼写错误或简单疏忽。 int numberOfFoobars;int N; 好很多。 我的错。对那些错误感到抱歉。这是我第一次问这样的问题哈哈哈 【参考方案1】:

字符串 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。

【讨论】:

工作就像一个魅力!感谢您的帮助。【参考方案2】:

正如 Joni 所说,您可以创建一个类似于 arr 的 lookupArr。由于要匹配的模式长度为 2,所以基本思路是:

首先创建一个长度为 n+1 的数组,所有值都用 0 填充。 其次,您尝试匹配字符串s中的模式AC,因此在arrC的索引处标记1 strong> 匹配字符串中的AC 时。 这样,在回答任何查询之前,您将只对数组进行一次迭代 对于每个查询,您可以在 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 秒内。

【讨论】:

为什么数组大小是n+1?如果我们可以分配 arr[0] = 0,然后分配 arr[i] = arr[i - 1] + 1 if arr[i] = 'C' &amp;&amp; arr[i] = 'A',会不会更容易。 @AhZong 这是因为字符串是 1 索引的。也适用于 l =1 a[r]-a[l-1] 仍然成立的情况。

以上是关于如何在给定起点和终点的字符串中查找子字符串的出现次数?的主要内容,如果未能解决你的问题,请参考以下文章

查找字符串中第 n 次出现的子字符串

从字符串中查找 C++ 第 n 次出现的子字符串

在图中运行 DFS 的问题

查找字符串中子字符串最后一次出现的索引

XSLT:查找字符串中的最后一次出现

在Java中的字符串中查找第二次出现的子字符串