【问题标题】:how to find the number of distinct subsequences of a string?如何找到字符串的不同子序列的数量?
【发布时间】:2011-07-06 07:22:51
【问题描述】:

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

例如,

输入
AAA
ABCDEFG
编解码器

输出
4
128
496

我该如何解决这个问题?

【问题讨论】:

  • 提醒:他们对子序列有一个有趣的定义。根据他们的定义,“AC”是“ABC”的子序列,根据我的理解,这不是。也许其他人都同意他们的观点,但我想我会强调一下。
  • @Joachim 为什么不呢? "AC" 是子序列,"AB" 和 "BC" 是子字符串,你可能会混淆子字符串和子序列
  • 也许我知道,也许他们的定义是正确的,我不争辩。我只是说直觉上我会以不同的方式解释它,如果有人和我有相同(可能是错误的)想法,我想提个醒。
  • 我不明白这个问题。你能解释一下你的例子吗?

标签: string algorithm dynamic-programming


【解决方案1】:

这是一个经典的动态规划问题。

让:

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] 的地方使用 a[i - 1]。如果您要提交代码,还记得将您的计算包装在 mod 函数中。这应该像这样实现:

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

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

【讨论】:

  • 这里实际上不需要dp数组,但它确实有助于解释这一点。
【解决方案2】:

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

想法是:如果字符串的所有字符都是不同的,则子序列的总数为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
}

【讨论】:

  • 感谢 mostafiz。 geeksforgeeks.org/count-distinct-subsequences它有完整的解决方案/
  • @mostafiz 为什么我们从last[s[i]] - 1] 中减去-1,即我们为什么要这样做dp[last[s[i]] - 1] 而不仅仅是dp[last[s[i]]]
  • @MostafizRahman 你能解释一下last occurrence 在你的回答中是什么意思吗?
  • @AjaySinghNegi 这意味着没有。计算 dp[i] 时的子序列数,当前字符在当时之前出现。因此,应该从现在的编号中扣除当时的许多子序列编号。子序列,以便在当前计算dp[i] = 2*dp[i-1]时不再重复计算它们。
  • 因为我刚刚接受了你的编辑,我似乎得到了你的答案,对吧?
【解决方案3】:
///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];
}

【讨论】:

    【解决方案4】:

    这是我的代码

    #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&lt;0(both are same),我到达空字符串并返回1,因为没有。空字符串的子序列数为 1。

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

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

    在总数之后。第一个 n-1 字符的子序列的个数已经计算出来,我们将它们加倍以获得第一个 n 字符。

    但是,假设当前遇到的字符(第 n 个字符)已经出现在之前的第一个 n-1 个字符中(即 - 在字符串 s[0....n-1] 中找到(注:s[n ] 是当前字符)),那么我们必须减去那些没有。可能从上一次遇到当前字符时(不包括)s 的那部分开始的子序列的数量,并且已经计算并存储在 L['this specific character'] 中。

    ie - BACA - 对于给定的字符串,第 4 个 A 之前已经遇到过(从递归返回时,我们首先遇到 B,然后是 A,然后是 C,最后A),所以我们扣除了编号。计算到(不包括)第二个A(这是2(因为A之前的子序列号是2))的子序列数。

    所以,每次我们都计算了编号。对于第一个 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).

    【讨论】:

      猜你喜欢
      • 2015-06-13
      • 2019-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-09
      • 1970-01-01
      • 1970-01-01
      • 2022-11-10
      相关资源
      最近更新 更多