【问题标题】:substring calculation in a string字符串中的子字符串计算
【发布时间】:2012-07-26 04:19:50
【问题描述】:

对于以下问题,我很难找到比 O(n^2) 更好的方法。

我得到一个字符串,例如xyxxz

现在我需要在给定字符串的每个前缀中找到匹配字符的总数。

这里,字符串的可能前缀是:

 xyxxz : matching characters is 5
 yxxz  : matching characters is 0 (since 1st character doesnt match)
 xxz   : matching characters is 1
 xz    : matching characters is 1
 z     : matching characters is 0

这应该是输出。 我做了以下代码:

cin>>str;
len=str.length();
for(i=0;i<len;i++){
    sum=0;
    k=i;
    for(int j=0;j<len;j++)
    {
        if(str[k] == str[j]){
           sum++;
           k++;
        }
        else
            break;
    }
    cout<<sum<<"  ";  //I get the output 5 0 1 1 0
}

但它是 O(n^2)。我想要一个更好的方法:可能是 O(n) 或 O(nlogn)。

提前致谢。

【问题讨论】:

  • 指出std::mismatch,IIRC 是线性复杂度。
  • 这会有所帮助。谢谢@chris :)
  • 如果你想要的话,this page 有一个可能的实现。这是您可以改进的起点。
  • 不是这回答了你的问题,但我认为你不需要else break; 行。
  • mismatch 需要 O(n).. 并且如前所述对每个可能的前缀应用不匹配,它又会是 O(n^2)。不是吗?

标签: c++ string algorithm suffix-array


【解决方案1】:

这可以使用以下过程在线性时间内完成:

  1. 构造后缀数组 SA 以及 LCP 数组(最长公共前缀数组)。后缀数组是字符串所有后缀的按字典顺序排序的列表(类似于您在示例中给出的列表,但按字典顺序排序。请注意,每个后缀由其在原始字符串中的起始位置表示,即每个后缀一个整数) . LCP 也是一个整数数组,长度与后缀数组相同。在每个位置 i>0 处,LCP[i] 是后缀数组的第 i 个后缀与第 (i-1) 个后缀共有的最长前缀;我们设置 LCP[0]:=0。

    后缀数组可以使用Skew算法(也称为DC算法)在线性时间内构建,并且可以在后缀数组旁边构建LCP数组,仍然是O(n)时间。请参阅SO post on state-of-the-art suffix array algorithms 了解更多想法和实现。

  2. 确定完整字符串在后缀数组中的位置(例如,通过线性扫描后缀数组以查找包含整数 0 的条目)。

  3. 从该位置开始沿着 LCP 数组向左和向右走,以识别每个后缀与完整字符串共有的最长前缀。我已经在this older SO post详细描述了这个过程。

备注 虽然这需要的内存和时间不超过 O(n),因此理论上是最优的,但这是一个非常复杂的过程,并且只有在字符串非常长的情况下才会在实践中有用。

【讨论】:

  • 是的 .. 看起来很复杂。我的字符串长度为 100 万。感谢您的帮助:)
【解决方案2】:

如果您为字符串构建后缀树,则可以遍历后缀树以查找匹配项的长度。与第一个字符不匹配的根节点的任何子节点的值都将为零,它的所有后代也是如此。然后,根节点的子节点的所有后代都至少有一个匹配,该匹配等于该边的匹配长度。

【讨论】:

  • 这是有帮助的。如果我明白你的想法,我需要遍历后缀树并获取每个前缀的总边数。这需要 O(nlogn)。如果我有请纠正我我弄错了。谢谢
  • @vjshah:是的,听起来不错。可能比 O(n*log(n)) 做得更好,因为树可以在 O(n) 时间内构建,但我不确定。
【解决方案3】:

使用后缀数组。后缀数组 DC3 会在 O(N) 时间内完成,其中 N 是原始字符串中的字符数。

【讨论】:

    【解决方案4】:
    int strcoll (register const char *s1, register const char *s2)
    {
        while (*s1 == *s2++) {
            if (*s1++ == '\0') {
                return 0;
            }
        }
        return *s1 - *--s2;
    }
    

    此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续下一个对,直到字符不同或到达表示字符串结尾的空字符。

    返回一个整数值,表示字符串之间的关系: 零值表示两个字符串相等。 大于零的值表示第一个不匹配的字符在 str1 中的值大于在 str2 中的值;而小于零的值则相反。

    【讨论】:

    • 但它不会提供比 O(n^2) 更好的解决方案。你必须使用字符串和字符串的每个前缀来执行 strcoll。所以要获取每个前缀然后是 strcoll,它将是 O (n^2).samething 可以用 strcmp 或 str.compare() 来完成。
    【解决方案5】:

    我不擅长计算 O 值。但这是您需要的另一种实现。我觉得效率更高一点。

    cin >> str;
    
    len=str.length();
    for(i=0;i<len;i++)
    {
        sum=0;
        k=0;
    
        while(str[k + sum] == str[i + sum])
        {
                sum++;
        }
        cout << sum ;
    
    }
    

    【讨论】:

    • while 是一个无限循环。又是 O(n^2)
    猜你喜欢
    • 2023-04-06
    • 2023-03-28
    • 1970-01-01
    • 2021-01-24
    • 2017-10-20
    • 2019-09-14
    • 1970-01-01
    相关资源
    最近更新 更多