182 竞赛

找到所有好字符串

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

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

由于答案可能很大,请你返回答案对 10^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 == n
s2.length == n
s1 <= s2
1 <= n <= 500
1 <= evil.length <= 50
所有字符串都只包含小写英文字母。

 

/**
 * @param {number} n
 * @param {string} s1
 * @param {string} s2
 * @param {string} evil
 * @return {number}
 * 单个字母的字典序就是排在字母表前面的字母小,排在字母表后面的字母大。
 * 字符串的字典序是从第一个字符开始往后一一对比,如果两个字符相等就比较打下一个;
 * 直到两个字符不等或其中一个字符串没有更多字母,这时长度大的字符串大。
 */

const mod = 10 ** 9 + 7

let cache = null

var findGoodStrings = function(n, s1, s2, evil) {
    /**
     * 9个bit,生成的字符串s最长为500个字符, 需要 2 ** 9 > 500
     * 6个bit,evil串最长长度为50, 需要 2 ** 6 > 50
     * leftBound 1个bit
     * rightBound 1个bit
     * 总共17个bit,记录生成的s串长度为mainTraver、匹配evil的长度为evilMatch、s中最后一个字符
     * 与下、上界是否接壤的的每种情况下生成的合法字符串的数目
     * 深度优先遍历的特点是,遍历是从第1层开始的,生成结果却是从最后一层开始倒着生成的
     * 所以cache记录的是所有已生成的完整的s串中, 前缀长度为mainTraver、evil匹配长度为evilMatch、
     * 以s的前缀长度mainTraver、evil已匹配的长度evilMatch、s的前缀最后一个字符接壤s1、s2的下标为mainTravel - 1的字符做上下界的情况
     * 四个维度做category分类统计的s合法串的数目
     * 所以最后的答案就是s串前缀长度mainTravel为0、evil已匹配长度为0、s串前缀最有后一个字符
     * 上下界都接壤的类目下合法s串的数目
     */
    cache = new Array(1 << 17).fill(-1)
    return dfs(0, 0, n, s1, s2, evil, true, true, computeNext(evil))
};
/**
 * 深度优先遍历:
 * 解法是从s1和s2的第1个字符开始从左到右遍历到最后一个字符
 * 每遍历一个位置i(i的范围是[0, n], 0表示还没开始遍历,是初始条件,n表示遍历完了), 
 * 当前位置的字符可以从from变化到to,from由
 * i - 1选择的字符是否等于s1[i - 1]和s1[i]决定,同理to由i - 1选择
 * 的字符是否等于s2[i -1]和s2[i]决定。
 * i位置每选择一个字符就要确定遍历到i位置为止生成的字符串中evil能匹配的长度。如果evilMatch
 * 灯evil.length,说明当前生成的字符串和以这个字符串为前缀的字符串都是非法的,直接返回0
 * 如果s1已经遍历到第n个字符,那么说明生成了一个完整的长度为n的合法的字符,返回1.
 * 初始条件是s1中遍历到第0个字符、evil匹配长度是0、上一个选择的字符既挨着上界又挨着下界(因为true && 任意值的话真假值由后面条件决定)
 * mainTravel 上一次dfs已生成的s串的长度,初始传入0,表示初始条件是一个字符都没有生成
 * evilMatch evil中与上一次已生成的s串匹配的长度,初始值同样是0
 * n
 * s1
 * s2
 * evil, 这四个参数不解释,不是猪就不用说
 * leftBound 上一个位置选择的字符是否挨着下界, 1表示挨下界 0不挨下界
 * rightBound 上一个位置选择的字符是否挨着上界 1挨上界
 * next, 如果选择的当前字符与evil中当前待比较的那个字符不相等, 下一个我要用evil中哪个下标的字符
 * 来跟当前选择的字符做比较,实际上也就得到了选择某个字符延长生成的s串后evil与新生成的串匹配的长度
 */
function dfs(mainTravel, evilMatch, n, s1, s2, evil, leftBound, rightBound, next) {
    // 如果上一次dfs已生成的s串, evil全部能匹配到, 那么已这个s串为前缀的串都非法,
    // 直接返回0,这个分支也不用再继续遍历了下去了
    if(evilMatch === evil.length) return 0
    // 这表示生成了一个完整的s串并且是合法的,dfs遍历到最后生成初始结果
    if(mainTravel === n) return 1
    let key = generateKey(mainTravel, evilMatch, leftBound, rightBound)
    // 因为四个类目的任意组合只会出现一次,不会有重复,所以如果已经统计好了这个类目下的数据
    // 那就可以直接复用以前的结果
    if(cache[key] !== -1) return cache[key]
    let from = leftBound ?  s1.charCodeAt(mainTravel) : 97
    let to = rightBound ?  s2.charCodeAt(mainTravel) : 122
    let res = 0
    for(let i = from; i <= to; i++) {
        let c = String.fromCharCode(i)
        /**
        * 找到evil与当前生成的字符串能匹配的长度
        * 实际上这道题目里evilMatch只会变长或保持不变、不会变短
        */
        let j = evilMatch
        while((j > 0) && (evil[j] !== c)) j = next[j - 1]
        if(evil[j] === c) j++
        res += dfs(mainTravel + 1, j, n, s1, s2, evil, leftBound && (i === from), rightBound && (i === to), next)
        res %= mod
    }
    cache[key] = res
    return res
}

// 生成cache的key
function generateKey(mainTravel, evilMatch, leftBound, rightBound) {
    return (mainTravel << 8) | (evilMatch << 2) | ((leftBound ? 1 : 0 ) << 1) | (rightBound ? 1 : 0)
}

 /**
  * 到某一个下标为止, 和某个后缀相等的前缀的长度的最大值(自己等于自己不算,
  * 也就是长度最大值是下标,不是下标 + 1)。
  * 也就是如果evil当前下标的指示的字符与主串中当前字符不相等, evil要往右
  *  滑动多少个位置。也就是下一步evil用哪个下标指示的值去与主串中的当前位置的字符做比较
  */
function computeNext(evil) {
    let n = evil.length
    let arr = new Array(n).fill(0)
    arr[0] = 0
    let j = 0
    for(let i = 1; i < n; i++) {
        while((j > 0) && (evil[i] !== evil[j])) j = arr[j - 1]
        if(evil[i] === evil[j]) arr[i] = ++j
        
    }
    return arr
}

  

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-good-strings
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

posted @ 2020-04-19 01:28  土豆zhang  阅读(169)  评论(0编辑  收藏  举报