找到所有好字符串
给你两个长度为 n 的字符串 s1 和 s2 ,以及一个字符串 evil 。请你返回 好字符串 的数目。
好字符串 的定义为:它的长度为 n ,字典序大于等于 s1 ,字典序小于等于 s2 ,且不包含 evil 为子字符串
一. 数位dp + KMP算法
const int MOD = 1e9 + 7;
class Solution {
public:
int findGoodStrings(int n, string s1, string s2, string evil) {
//遍历下标,在s1[i]和s2[i]之间枚举字符
//不受限的情况下,可以取26个字母
//后面能否枚举,受前面的制约,想某种方法将前面的特征存下,用作dp数组的第二维,这里用前面已匹配的字符位数
int len = evil.size(); int memo[n][len];
memset(memo,-1,sizeof(memo));
//获取next数组
vector<int> fail(len, -1);//初始无前一元素,跳转-1,同时表示无相等元素,因为后面还要增
for (int i = 1; i < len; i++){//从1开始,评估每个位置应跳转的地方
int j = fail[i - 1];//前一元素的跳转位置
while (j != -1 && evil[j + 1] != evil[i]) //跳转后的后一元素继续比较当前元素
j = fail[j];//,匹配失效,继续跳转,压缩转移位置
if (evil[i] == evil[j + 1]) fail[i] = j + 1;//相等记录该位置
//否则跳转到-1
}
function<int(int, int ,bool, bool)> f = [&](int i,int j,bool downlimit,bool uplimit) -> int {//视情况记录已遍历值的某种特征,如1的个数,mask状态,
if (j == len) return 0; //已枚举字符串包含evil,不合法,返回0
if (i == n ) return 1;//边界条件,枚举出了一个满足条件的字符串
if (!downlimit&&!uplimit&&memo[i][j]!= -1) //不受限且已经存储过,直接剪枝返回
return memo[i][j];
int res = 0; //计算个数
char down = downlimit? s1[i]: 'a';// 根据受限与否决定枚举数下界
char up = uplimit? s2[i] : 'z'; // 根据受限与否决定枚举数上界
//做选择,因为每次移动要重新判断之前的字符串匹配到了哪一位,这里使用kmp算法,计算匹配失效转移位
for (char c = down; c <= up; c++){
int nextj = j-1;
//对于枚举的字符,当前位不匹配,进行转移
while(nextj!=-1&&evil[nextj+1]!=c) nextj = fail[nextj];
//出循环再判断一次,因为nextj为-1后不会再判断移动
if(evil[nextj+1]==c) nextj = nextj+1;
res += f(i + 1, nextj + 1, downlimit&&c==down,uplimit&&c==up);//移动到下一位,判断限制情况
res = res % MOD;//取模
}
if (!downlimit&&!uplimit) //记录非限制数
memo[i][j] = res;
return res;
};
return f(0,0,true,true); //从下标0开始,刚开始受限
}
};