题目
1397. 找到所有好字符串
给你两个长度为 n 的字符串 s1 和 s2 ,以及一个字符串 evil 。请你返回 好字符串 的数目。
好字符串 的定义为:它的长度为 n ,字典序大于等于 s1 ,字典序小于等于 s2 ,且不包含 evil 为子字符串。
由于答案可能很大,请你返回答案对 取余的结果。
示例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
提示
所有字符串都只包含小写英文字母。
题解
关于这个题,是个比较典型的数位动态规划的题,之前做的题大部分是关于数字的数位dp,而现在要的就是字符串的数位dp。
设为数位dp的数组,其中 表示第pos个位置的字符总共有的数量,表示的是匹配的状态,即能够匹配到数组的位置。表示此时能够选择字符的范围,即当前字符选择的时候是否有限制。
对于我们用四个数字表示
- 表示此时的字符选择是没有限制的,即可以选择的范围为
- 表示此时的字符选择是有下限的,所以选择的范围是
- 表示此时的字符选择是有上限的,所以可以选择的范围是
- 表示此时的字符既有上限又有下限。这种情况只有当
而对于表示匹配字符的状态,由于的长度最长为50,所以可以生成字符串的next数组。那么当匹配不成立的时候,就可以直接进行跳转。
设一个记忆数组表示当匹配的位置为时,下一个字符为时,可以跳转的位置,因为在整个搜索的过程中,可能需要多次调用这个数组,而这个数组大小为,因此没必要每次都计算。
对于如何生成next
的数组,小伙伴们可以去搜索与算法相关的博客查看。
提示
在leetcode提交的过程中,要注意对全局变量的初始化
。
代码
//数位dp的 pos,stats, bound
var dp [510][55][4]int64
//匹配数据的next数组
var next [50]int
var memo [55][27]int
var ns,m int
const mod int64 = 1e9+7
func kmp(s string) {
for i:=1;i<m;i++ {
j := next[i-1]
for j!=0 && s[j] != s[i] {
j = next[j-1]
}
if s[i] == s[j] {
next[i] = j+1
}
}
}
func Init() {
for i:=0;i<=ns;i++{
for j:=0;j<=m;j++{
for k:=0;k<4;k++{
dp[i][j][k] = -1
}
}
}
for i:=0;i<=m;i++ {
next[i] = 0
for j:=0;j<=26;j++{
memo[i][j] = -1
}
}
}
func memory(evil string, ch byte, stats int )int {
if memo[stats][ch-97] != -1 {
return memo[stats][ch-97]
}
for stats != 0 && evil[stats] != ch {
stats = next[stats-1]
}
if evil[stats] == ch {
memo[stats][ch-97] = stats + 1
} else {
memo[stats][ch-97] = 0
}
return memo[stats][ch-97]
}
func dfs(pos int , stats int , bound int, s1 string , s2 string, evil string) int64 {
//如果能够匹配完evil就说明不能成立
if stats == m {
return 0
}
// 如果匹配成功,则说明字符构建完成
if pos == ns {
return 1
}
if dp[pos][stats][bound] != -1 {
return dp[pos][stats][bound]
}
dp[pos][stats][bound] = 0
var l ,r byte
// 检测是否存在下界
if bound & 1 != 0 {
l = s1[pos]
} else {
l = 'a'
}
// 检测是否存在上界
if bound & 2 != 0{
r = s2[pos]
} else {
r = 'z'
}
for i:=l;i <= r;i++{
next_stats := memory(evil,i, stats)
var next_bound int = 0
// 根据之前的状态来推断下一个状态
if bound & 1 !=0 {
if i == s1[pos] {
next_bound = 1
}
}
if bound & 2 !=0 {
if i == s2[pos] {
next_bound = next_bound + 2
}
}
dp[pos][stats][bound] += dfs(pos+1, next_stats, next_bound, s1,s2, evil)
dp[pos][stats][bound] %= mod
}
return dp[pos][stats][bound]
}
func findGoodStrings(n int, s1 string, s2 string, evil string) int {
ns = n
m = len(evil)
Init()
kmp(evil)
return int(dfs(0,0,3,s1,s2,evil))
}