【Leetcode】940. Distinct Subsequences II

这篇博客介绍了如何利用动态规划和序列自动机解决计算字符串本质不同子序列数量的问题。提供了三种不同的算法实现,包括动态规划的基本思路和优化,以及序列自动机的方法,所有算法的时间复杂度均为O(n)。博客内容深入浅出,详细解释了每种方法的逻辑和代码实现。
摘要由CSDN通过智能技术生成

题目地址:

https://leetcode.com/problems/distinct-subsequences-ii/

给定一个长 n n n的字符串 s s s,问 s s s的本质不同的非空子序列的个数。答案模 1 0 9 + 7 10^9+7 109+7后返回。题目保证 s s s只含英文小写字母。

法1:动态规划。设 f [ k ] f[k] f[k] s s s的长 k k k的前缀的本质不同的子序列的个数(含空)。那么 f [ 0 ] = 1 f[0]=1 f[0]=1,接下来考虑怎么由 f [ k ] f[k] f[k]得出 f [ k + 1 ] f[k+1] f[k+1]。以下 s [ i ] s[i] s[i]表示 s s s的第 i i i个字符。先假设已经得出所有的 f [ k ] f[k] f[k]个不同的子序列,那么在 f [ k + 1 ] f[k+1] f[k+1]中,它们仍然是合法的子序列,应该包含在内;此外,它们可以后面接上 s [ k + 1 ] s[k+1] s[k+1],成为另外 f [ k ] f[k] f[k]个不同的子序列,当中可能有重复的部分,如果 s [ l ] s[l] s[l] s s s中在 s [ k + 1 ] s[k+1] s[k+1]之前的最后一次出现,那么对于 f [ l − 1 ] f[l-1] f[l1]的那些不同子序列,后面接上 s [ l ] s[l] s[l]和接上 s [ k + 1 ] s[k+1] s[k+1]是等价的,所以此时 f [ k + 1 ] = 2 f [ k ] − f [ l − 1 ] f[k+1]=2f[k]-f[l-1] f[k+1]=2f[k]f[l1]。当然如果 s [ k + 1 ] s[k+1] s[k+1]在之前没出现过,则 f [ k + 1 ] = 2 f [ k ] f[k+1]=2f[k] f[k+1]=2f[k]。每个字符的最后一次出现的位置可以用一个哈希表记录。代码如下:

public class Solution {
    public int distinctSubseqII(String s) {
        int n = s.length(), MOD = (int) (1e9 + 7);
        // 记录每个字母出现的最后位置,下标从1开始
        int[] pos = new int[26];
        long[] f = new long[n + 1];
        f[0] = 1;
        for (int i = 1; i <= n; i++) {
            char ch = s.charAt(i - 1);
            f[i] = f[i - 1] * 2 % MOD;
            if (pos[ch - 'a'] > 0) {
                f[i] = (f[i] - f[pos[ch - 'a'] - 1] + MOD) % MOD;
            }
            
            pos[ch - 'a'] = i;
        }
        
        return (int) (f[n] - 1);
    }
}

时空复杂度 O ( n ) O(n) O(n)

法2:序列自动机 + 记忆化DFS。设 s [ i ] s[i] s[i] s s s的第 i i i个字母,此处 i i i 1 1 1开始计数, s [ 0 ] s[0] s[0]定义为空字符。构造 s s s的序列自动机,即 A [ u ] [ c ] A[u][c] A[u][c]指的是 s [ u ] s[u] s[u]之后字母 c c c第一次出现的位置,如果未出现则定义为 0 0 0 f [ u ] f[u] f[u] s [ u + 1 : ] s[u+1:] s[u+1:]的本质不同子序列的个数(含空序列)。那么 f [ u ] f[u] f[u]两种可能,或者空序列,或者是接上 s [ u + 1 : ] s[u+1:] s[u+1:]之后的第一个'a', 'b'等等如果有的话,然后再接 s [ A [ u ] [ c ] ] s[A[u][c]] s[A[u][c]]之后的本质不同子序列,其个数是 f [ A [ u ] [ c ] ] f[A[u][c]] f[A[u][c]],即 f [ u ] = 1 + ∑ A [ u ] [ c ] > 0 f [ A [ u ] [ c ] ] f[u]=1+\sum_{A[u][c]>0} f[A[u][c]] f[u]=1+A[u][c]>0f[A[u][c]]代码如下:

public class Solution {
    public int distinctSubseqII(String s) {
        int n = s.length(), MOD = (int) (1e9 + 7);
        // 构造序列自动机
        int[][] dfa = new int[n + 1][26];
        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j < 26; j++) {
                dfa[i][j] = dfa[i + 1][j];
            }
            dfa[i][s.charAt(i) - 'a'] = i + 1;
        }
        
        // 还要把空序列减掉
        return dfs(0, dfa, MOD, new int[n + 1]) - 1;
    }
    
    private int dfs(int u, int[][] dfa, int MOD, int[] f) {
        if (f[u] > 0) {
            return f[u];
        }
    
    	f[u] = 1;
        for (int i = 0; i < 26; i++) {
            if (dfa[u][i] > 0) {
                f[u] = (f[u] + dfs(dfa[u][i], dfa, MOD, f)) % MOD;
            }
        }
        
        return f[u];
    }
}

时空复杂度 O ( n ) O(n) O(n)

法3:动态规划。还有另一种思路,设 f [ i ] [ c ] f[i][c] f[i][c] s s s i i i个字母中的以字母 c c c为结尾的不同子序列的个数。那么答案就是 ∑ c = 0 26 f [ n ] [ c ] \sum_{c=0}^{26} f[n][c] c=026f[n][c](我们用 0 0 0指代字母a,以此类推)。考虑 f [ i ] [ c ] f[i][c] f[i][c],设 s s s下标从 1 1 1开始,如果 c ≠ s [ i ] c\ne s[i] c=s[i],那么就不可能以 s [ i ] s[i] s[i]结尾,所以不同的方案数就是 f [ i − 1 ] [ c ] f[i-1][c] f[i1][c];否则, f [ i ] [ c ] f[i][c] f[i][c]的任意一个方案都可以将最后一个字母看成是使用了 s [ i ] s[i] s[i],这样可以考虑倒数第二个字母是什么,可以不存在倒数第二个,那么方案数就是 1 1 1,也可以存在倒数第二个字母,方案数为 ∑ c = 0 26 f [ i − 1 ] [ c ] \sum_{c=0}^{26} f[i-1][c] c=026f[i1][c],所以总方案数即为 1 + ∑ c = 0 26 f [ i − 1 ] [ c ] 1+\sum_{c=0}^{26} f[i-1][c] 1+c=026f[i1][c]。一路递推出 f [ n ] f[n] f[n]即可。代码如下:

class Solution {
 public:
  int distinctSubseqII(string s) {
    int MOD = 1e9 + 7;
    int n = s.size(), f[n + 1][26];
    memset(f, 0, sizeof f);
    for (int i = 1; i <= n; i++) {
      f[i][s[i - 1] - 'a'] = 1;
      for (int j = 0; j < 26; j++)
        if (s[i - 1] - 'a' != j)
          f[i][j] = f[i - 1][j];
        else
          for (int k = 0; k < 26; k++) f[i][j] = (f[i][j] + f[i - 1][k]) % MOD;
    }

    int res = 0;
    for (int x : f[n]) res = (res + x) % MOD;
    return res;
  }
};

时空复杂度 O ( n ) O(n) O(n)

我们发现,对于 f [ i ] f[i] f[i],除了 f [ i ] [ s [ i ] ] f[i][s[i]] f[i][s[i]],其余的 f [ i ] [ c ] = f [ i − 1 ] [ c ] f[i][c]=f[i-1][c] f[i][c]=f[i1][c],代码可以优化:

class Solution {
 public:
  int distinctSubseqII(string s) {
    int MOD = 1e9 + 7;
    int n = s.size(), f[26] = {};
    for (int i = 1; i <= n; i++) {
      int t = 1;
      for (int k = 0; k < 26; k++) t = (t + f[k]) % MOD;
      f[s[i - 1] - 'a'] = t;
    }

    int res = 0;
    for (int x : f) res = (res + x) % MOD;
    return res;
  }
};

时间复杂度一样,空间 O ( 1 ) O(1) O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值