leetcode 1397. Find All Good Strings

给你两个长度为 n 的字符串 s1 和 s2 ,以及一个字符串 evil 。请你返回 好字符串 的数目。

好字符串 的定义为:它的长度为 n ,字典序大于等于 s1 ,字典序小于等于 s2 ,且不包含 evil 为子字符串。

由于答案可能很大,请你返回答案对 10^9 + 7 取余的结果。

示例 1:

输入:n = 2, s1 = "aa", s2 = "da", evil = "b"
输出:51
解释:总共有 25 个以 'a' 开头的好字符串:"aa","ac","ad",...,"az"。还有 25 个以 'c' 开头的好字符串:"ca","cc","cd",...,"cz"。最后,还有一个以 'd' 开头的好字符串:"da"。

示例 2:

输入:n = 8, s1 = "leetcode", s2 = "leetgoes", evil = "leet"
输出:0
解释:所有字典序大于等于 s1 且小于等于 s2 的字符串都以 evil 字符串 "leet" 开头。所以没有好字符串。
示例 3:

输入:n = 2, s1 = "gx", s2 = "gz", evil = "x"
输出:2

限制:

s1.length == n
s2.length == n
s1 <= s2
1 <= n <= 500
1 <= evil.length <= 50
所有字符串都只包含小写英文字母。

解决方法: dp+KMP

时间复杂度: \(O(n*m*\vert\sum\vert)\)

\(n:=s1.length\)
\(m:=evil.length\)
\(\sum\) 是字母表 a,b,c...z

空间复杂度: \(O(n*m)\)

思路

  1. 在a和b之间求满足条件的长度为n的字符串,这个显然是用数位dp处理的,然后这里因为是介于两个字符串字典序中间的字符串,不是求两个数之间满足条件的数,这里我没有选择使用只考虑上界的模版,计算\(dp(l,r)=dp(0,r)-dp(0,l-1)\),而是使用同时考虑贴上界和贴下界的模版。
  2. 这里使用记忆化递归来存储已经计算好的值,状态为\((pos,stats,limit)\),stats表示之前的字符和evil字符串匹配的程度,这个概念是KMP算法里面的前缀函数,如何由next数组,和当前输入字母,求出前缀函数值,详见根据前缀函数构建一个自动机

代码

class Solution {
public:
    int findGoodStrings(int n, string s1, string s2, string evil) {
        const int kMod=1e9+7;
        const int m=evil.size();
        vector<int> fail(m);
        vector<vector<int>> trans(m,vector<int>(26));
        vector<vector<vector<int>>> cache(n,vector<vector<int>>(m,vector<int>(4,-1)));
        fail[0]=0;
        for(int i=1,j=0;i<m;++i){
            while(j>0&&evil[i]!=evil[j]) j=fail[j-1];
            if(evil[i]==evil[j]) ++j;
            fail[i]=j;
        }
        for(int i=0;i<m;++i)
            for(int c=0;c<26;++c){
                int j=i;
                while(j>0&&c+'a'!=evil[j]) j=fail[j-1];
                if(c+'a'==evil[j]) ++j;
                trans[i][c]=j;
            }
        function<int(int,int,int)> dp=[&](int pos,int stats,int limit){
            if(stats==m) return 0;
            if(pos==n){
                return 1;
            }
            int& ans=cache[pos][stats][limit];
            if(ans!=-1) return ans;
            ans=0;
            char down=(limit&1?s1[pos]:'a');
            char up=(limit&2?s2[pos]:'z');
            for(char ch=down;ch<=up;++ch){
                int next_stats=trans[stats][ch-'a'];
                int next_limit=(limit&1?ch==s1[pos]:0)^(limit&2?(ch==s2[pos])<<1:0);
                ans+=dp(pos+1,next_stats,next_limit);
                ans%=kMod;
            }
            return ans;
        };
        return dp(0,0,3);
    }
};
posted @ 2020-06-05 17:33  xcjm  阅读(190)  评论(0)    收藏  举报