Leetcode——1397. 找到所有好字符串

题目

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

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

由于答案可能很大,请你返回答案对 109+710^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==ns1.length == n
s2.length==ns2.length == n
s1s2s1 \le s2
1n5001 \le n \le 500
1evil.length501 \le evil.length \le 50
所有字符串都只包含小写英文字母。

题解

关于这个题,是个比较典型的数位动态规划的题,之前做的题大部分是关于数字的数位dp,而现在要的就是字符串的数位dp。
dp[pos][stats][bound]dp[pos][stats][bound]为数位dp的数组,其中 pospos表示第pos个位置的字符总共有的数量,statsstats表示的是匹配evilevil的状态,即能够匹配到evilevil数组的位置。boundbound表示此时能够选择字符的范围,即当前字符选择的时候是否有限制。
对于boundbound我们用四个数字表示

  • 00 表示此时的字符选择是没有限制的,即可以选择的范围为aza \sim z
  • 11 表示此时的字符选择是有下限的,所以选择的范围是s1[pos]zs1[pos] \sim z
  • 22 表示此时的字符选择是有上限的,所以可以选择的范围是as2[pos]a \sim s2[pos]
  • 33 表示此时的字符既有上限又有下限。这种情况只有当s1[pos]s2[pos]s1[pos] \sim s2[pos]

而对于statsstats表示匹配evilevil字符的状态,由于evilevil的长度最长为50,所以可以生成字符串evilevil的next数组。那么当匹配不成立的时候,就可以直接进行跳转。

设一个记忆数组mem[e_pos][n_char]mem[e\_pos][n\_char]表示当匹配evilevil的位置为e_pose\_pos时,下一个字符为n_charn\_char时,可以跳转的位置,因为在整个搜索的过程中,可能需要多次调用这个数组,而这个数组大小为mem[50][26]mem[50][26],因此没必要每次都计算。

对于如何生成next的数组,小伙伴们可以去搜索与KMPKMP算法相关的博客查看。

提示

在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))

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章