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

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