如何查找字符串的不同子序列的数量?

时间:2011-03-01 07:09:21

标签: string algorithm dynamic-programming

这是另一个spoj problem,它询问如何查找字符串的不同子序列的数量?

例如,

  

输入
  AAA
  ABCDEFG
  CODECRAFT

     

输出
4
128
496

我该如何解决这个问题?

4 个答案:

答案 0 :(得分:58)

这是一个经典的动态编程问题。

让:

dp[i] = number of distinct subsequences ending with a[i]
sum[i] = dp[1] + dp[2] + ... + dp[i]. So sum[n] will be your answer.
last[i] = last position of character i in the given string.

空字符串有一个子序列,因此dp[0] = 1

read a
n = strlen(a)
for i = 1 to n
  dp[i] = sum[i - 1] - sum[last[a[i]] - 1]
  sum[i] = sum[i - 1] + dp[i]
  last[a[i]] = i

return sum[n]

<强>解释

dp[i] = sum[i - 1] - sum[last[a[i]] - 1]

最初,我们假设我们可以将a[i]附加到以前字符结尾的所有子序列,但这可能违反了计算的子序列需要区分的条件。请记住,last[a[i]]为我们提供了迄今为止出现的最后一个位置a[i]。我们过度计算的唯一子序列是前一个a[i]附加到的子序列,因此我们将其减去。

sum[i] = sum[i - 1] + dp[i]
last[a[i]] = i

根据其定义更新这些值。

如果索引从0开始,请在我使用a[i - 1]的任何地方使用a[i]。如果您要提交代码,请记住将计算包装在mod函数中。这应该像这样实现:

mod(x) = (x % m + m) % m

为了正确处理某些语言中的负值(例如C / C ++)。

答案 1 :(得分:30)

这个问题有一个更简单的解决方案。

这个想法是:如果字符串的所有字符都是不同的,则子序列的总数是2^n.现在,如果我们发现之前已经发生的任何字符,我们应该只考虑它的最后一次出现(否则序列获胜)不明白。因此,我们必须减去由于之前发生的子序列的数量。

我的实现是这样的:

read s
dp[0] = 1
len = strlen(s)
last[s.length()] = {-1} //declaring `last` array with same as length of string `s` and all elements initialized with -1.

for (i = 1; i <= len; i++) 
{
    dp[i] = (dp[i - 1] * 2)
    if (last[s[i]] > 0) dp[i] = (dp[i] - dp[last[s[i]] - 1])
    last[s[i]] = i
}

答案 2 :(得分:0)

这是我的 CODE

#include<iostream>
typedef long long ll;

ll fun(std::string s,ll visited[256],ll n,ll L[]){  

    ll ans=0;
    if(n<0){
        return 1;
    }
    //std::cout<<s.substr(0,n+1)<<" "<<n<<endl;

    ans=fun(s,visited,n-1,L);
    L[n]=ans;
    ans=ans*2;
    if(visited[int(s[n])]>=0){
        ans -= L[visited[int(s[n])]];
    }
    visited[int(s[n])]=n;

    return ans;

}
int main(){

    std::string s;
    std::cin>>s;
    ll n=s.length();

    ll visited[256];
    ll L[n];
    memset(visited,-1,sizeof(visited));
    memset(L,-1,sizeof(L));

    std::cout<<fun(s,visited,n-1,L);

    return 0;
}

说明

我从字符串的末尾(即从最后一个元素到第一个元素)进行扫描,因此发送前n-1个字符以进行递归的进一步扫描。

一旦n==-1 or n<0(both are same),我到达空字符串并返回1,因为没有。空字符串的子序列的数量为1。

因此,从递归返回时,我们知道将当前的非重复字符添加到前一个字符串将使no翻倍。子序列。之所以会加倍,是因为现在我可以在所有先前子序列的末尾添加此字符。因此,withwithout这个字符表示所有先前子序列的两倍。

假设当前字符不是重复字符,我将前一个字符乘以。 2的子序列。

总数之后已计算出前n-1个字符的子序列的数量,我们将它们对前n个字符进行了双倍处理。

但是,假设当前遇到的字符(第n个字符)已经出现在前面的前n-1个字符中(即-在字符串s [0 .... n-1]中找到(注:s [n]是当前字符)),那么我们必须减去那些数字。上次遇到此当前字符时,s的最高部分(不包括s)的子序列,并且已经计算并存储在L ['这个特定字符']中。

ie-BACA-对于给定的字符串,之前已经遇到了第四个A(从递归返回时,我们首先遇到B,然后是A ,然后是C,最后是A),因此我们减去了。最多计算(不包括)第二个A(为2(因为A之前的子序列数为2)的子序列)。

因此,每次我们计算出no。前n-1个字符的子序列,我们将它们存储在数组L中。

通知:L [k]存储编号。第k个索引之前的子序列的数量。

我使用了访问数组来检查我当前所在的给定字符是否已被扫描。

遇到当前字符时,我用当前位置的位置更新为n的访问数组。这样做是因为我们必须排除重复的序列。

注意visited[]用全-1初始化,因为字符串s中任何字符的位置都不为负(基于0的索引)。

摘要

How do you arrive at the number of duplicates? Let's say the last occurrence of current character at i, was at j'th position. Then, we will have duplicate subsequences: consider starting with i'th character and then all subsequences possible from [0,j-1] vs. starting at j'th character and then all subsequences possible from [0,j-1]. So, to eliminate this, you subtract the number of subsequences possible from upto (excluding) j with L[0]=1 mean that upto(excluding 0), no. of subseq are 1(empty string has 1 subsequence).

答案 3 :(得分:-2)

///i get wa 
int finding_dist_subs(int len,char data[])
{
  dp[0]=1;
  for(int i=1;i<len;i++)
  {
      dp[i]=(dp[i-1]*2+1)%1000000007;
      for(int j=i-1;j>=0;j--)
      {
          if(data[i]==data[j])
          {
              if(j!=0)
           dp[i]=(dp[i]-(dp[j-1])-1)%1000000007;
           else dp[i]=(dp[i]-1)%1000000007;

            break;
          }
      }
  }
  return dp[len-1];
}