Distinct Subsequences

本文介绍了一种计算一个字符串作为另一个字符串子序列出现次数的方法。通过三种不同思路:遍历、递归及动态规划来解决这一问题,并详细分析了每种方法的时间复杂度和适用场景。

题目

Given a string S and a string T, count the number of distinct subsequences of T in S.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of"ABCDE" while "AEC" is not).

Here is an example:
S = "rabbbit"T = "rabbit"

Return 3.

思路一:遍历

1、对于T中的每一个字符,找到在S中的位置,得到一个二维的数组 vector<vector<int>> myvector

2、找到满足条件 myvector[i][j]<myvector[i+1][k] 的所有排列。

例如      S="abaabcbc"   T="abc"   可以得到下列二维数组 myvector

1  3  4

  2  5  7

         6  8

然后对该数组进行广度优先搜索,可得到满足条件的数目。

class Solution {
public:
    int numDistinct(string S, string T) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        int lenS = S.size();
        int lenT = T.size();
        if(lenS<lenT)
            return 0;
        vector<vector<int> > myvector(lenT,vector<int>());                   
        for(int i=0;i<lenT;i++)
        {
            for(int j=i;j<lenS;j++)
                if(S[j]==T[i])
                    myvector[i].push_back(j);
        }
        return countbigger(myvector, -1, 0);            
    }
    
    int countbigger(vector<vector<int> > &myvector, int num, int level)
    {
        if(level==myvector.size()-1)
        {
            int count=0;
            for(int i=0;i<myvector[level].size();i++)
                if(myvector[level][i]>num)
                    count++;
            return count;
        }
        int count=0;
        for(int i=0;i<myvector[level].size();i++)
        {
            if(myvector[level][i]>num)
                count += countbigger(myvector, myvector[level][i], level+1);
        } 
        return count;
    }    
    
};


时间复杂度是 O(M*N)+...

所以对于 Juge Large 不能通过,只能通过小数据。

思路二:递归

class Solution {
public:
    int numDistinct(string S, string T) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        if(S.size()<T.size())
            return 0;
        int num=0;
        match(S,T,0,0,num);
        return num;
        
    }
    void match(string &S, string &T, int indS, int indT, int &num)
    {
        if(indS>=S.size() || indT>=T.size())
            return ;
        if(S[indS]==T[indT])
        {
            if(indT==T.size()-1)
                num++;
            else
                match(S,T,indS+1,indT+1,num);
        }
        match(S,T,indS+1,indT,num);
    }
    
};


大数据还是过不了。

思路三:动态规划

用迭代的方法实现动态规划:

class Solution {
public:
    int numDistinct(string S, string T) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        int lenS = S.length();
        int lenT = T.length();
        if(lenS<1 || lenT<1 || lenS<lenT) {
            return 0;
        }
        vector<vector<int>> num(lenT,vector<int>(lenS,0));
        if(S[0]==T[0]) num[0][0] = 1;
        for(int j=1;j<lenS;j++){
            if(S[j]==T[0]){
                num[0][j]=num[0][j-1]+1;
            } else {
                num[0][j]=num[0][j-1];
            }
        }
        
        for(int i=1;i<lenT;i++){
            for(int j=i;j<lenS;j++){
                if(T[i]==S[j]){
                    num[i][j]=num[i-1][j-1];
                }
                num[i][j]+=num[i][j-1];
            }
        }
        
        return num[lenT-1][lenS-1];        
    }
};


此时速度快多了。

T 串中前 i 个字母的子串 在 S 串中前 j 个字母的子串中含有的个数——>这样的问题记为 A[i][j]  , 可以得到递推式:

(1)首先 A[i][j] 是由 A[i][j-1] 到这一步的;(而不是由 A[i-1][j] ,这是因为 S 中j-1 个字符含有 T 的前 i 个字符个数,与 S 中前 j 个含有 T 的前 i 个字符是类似问题,有递推关系,需要匹配的子串没有变化;但是 S 中 j 个字符含有的 T 的前i个字符,与S中前 j 个字符含有 T 的前 i-1 个字符 就不是 相似之问题了,它们的解之间没有相关性)

(2)如果 T[i] = S[j] ,则 还有另外一种情况到这一步:由 A[i-1][j-1] 到这一步的;

 

还有其他的解释:

Let f(i, j) to be the number of distinct subsequences ofT(j:) inS(i:).

Consider the ith character in S.

If we can use it to match T[j], namelyS[i] == T[j],

then  f(i, j) = f(i+1, j+1).

If we do not want use it in our matching,       

then    f(i, j) = f(i+1, j).

Thus, f(i, j) = f(i+1, j) + (S[i] == T[j]) * f(i+1, j+1).

 

/*
Let f(i, j) to be the number of distinct subsequences ofT(j:) in S(i:).
Consider the ith character in S.
If we can use it to match T[j], namelyS[i] == T[j],
then  f(i, j) = f(i+1, j+1).
If we do not want use it in our matching,       
then    f(i, j) = f(i+1, j).
Thus, f(i, j) = f(i+1, j) + (S[i] == T[j]) * f(i+1, j+1).
*/
public class Solution {
    
    private int number = 0;
    
    public int numDistinct(String s, String t) {
        if(s == null || t == null){
            return 0;
        }
        if(s.length()<t.length()){
            return 0;
        }
        //matchDistinct(s, t, 0, 0);
        int[][] table = new int[s.length()][t.length()];
        if(s.charAt(0) == t.charAt(0)){
            table[0][0] = 1;
        }
        for(int i=1; i<s.length(); i++){
            if(s.charAt(i) == t.charAt(0)){
                table[i][0] = table[i-1][0] +1;
            } else {
                table[i][0] = table[i-1][0];
            } 
        }
        for(int i=1; i<s.length(); i++){
            for(int j=1; j<t.length(); j++){
                if(s.charAt(i) == t.charAt(j)){
                    table[i][j] = table[i-1][j] + table[i-1][j-1];
                } else {
                    table[i][j] = table[i-1][j];
                } 
            }
        }
        return table[s.length()-1][t.length()-1];
        //return number;
    }
    
    private void matchDistinct(String s, String t, int m, int n){
        if(m>=s.length() || n>=s.length()){
            return;
        }
        if(s.charAt(m) == t.charAt(n)){
            if(n == t.length()-1){
                number ++;
            } else {
                matchDistinct(s, t, m+1, n+1);
            }
        }
        matchDistinct(s, t, m+1, n);
    }
    
}



优化空间后得到代码:

class Solution {
public:
    int numDistinct(string S, string T) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        int lenS = S.length();
        int lenT = T.length();
        if(lenS<1 || lenT<1 || lenS<lenT) {
            return 0;
        }
        vector<int> num(lenT+1,0);
        num[0]=1;
        for(int j=0;j<lenS;j++){
            for(int i=lenT-1;i>=0;i--){
                if(T[i]==S[j]){
                    num[i+1]+=num[i];
                }               
            }
        }        
        return num[lenT];        
    }
};


 

 

### 关于回文子序列的算法及其示例 #### 定义与概念 回文是指正读和反读都相同的字符序列。对于给定字符串中的任意字符组合形成的子序列,如果该子序列满足上述条件,则称为回文子序列。 #### 动态规划求解最长回文子序列 为了找到一个字符串中最长的回文子序列,可以采用动态规划的方法来解决这个问题。设 `dp[i][j]` 表示从第 i 到 j 的子串内的最长回文子序列长度: - 当 s[i]==s[j] 时, dp[i][j]=dp[i+1][j−1]+2; - 否则, dp[i][j]=max(dp[i+1][j],dp[i][j−1]). 最终的结果保存在 `dp[0][len(s)-1]` 中[^3]. ```python def longest_palindromic_subseq(s: str) -> int: n = len(s) # 创建二维数组用于存储中间结果 dp = [[0]*n for _ in range(n)] # 初始化单个字符的情况 for i in range(n): dp[i][i] = 1 # 填充表格 for length in range(2, n + 1): for start in range(n - length + 1): end = start + length - 1 if s[start] == s[end]: dp[start][end] = dp[start+1][end-1] + 2 else: dp[start][end] = max(dp[start+1][end], dp[start][end-1]) return dp[0][-1] ``` 此方法的时间复杂度为 O(n²),空间复杂度同样为 O(n²). #### 枚举所有可能的回文子序列 除了寻找最长的回文子序列外,还可以通过枚举的方式找出所有的不同回文子序列。这种方法适用于较短的输入字符串,并且可以通过位掩码技术实现高效的遍历。 ```python from collections import defaultdict def count_distinct_palindrome_subsequences(text: str) -> list[str]: results = set() memo = {} def backtrack(start=0, path=""): nonlocal text, results, memo key = (start, path) if key not in memo: temp_set = {path} if path == path[::-1] else {} for index in range(start, len(text)): new_path = path + text[index] if new_path == new_path[::-1]: temp_set.add(new_path) temp_set |= backtrack(index + 1, new_path) memo[key] = temp_set results.update(memo[(start, path)]) return memo[(start, path)] backtrack() return sorted(list(results)) ``` 这段代码会返回按字典序排列的不同回文子序列列表.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值