題目
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))
}