zoukankan      html  css  js  c++  java
  • 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
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 相关阅读:
    关于进程exit后,内存释放释放的实践
    《C语言基础日常笔记》
    阿里巴巴重磅推出,让你的电脑变成云服务器,只要 5 分钟! 5 分钟! 5 分钟!
    vs2010下使用绘图控件MsChart的方法
    归纳整理Linux下C语言常用的库函数----文件操作
    归纳整理Linux下C语言常用的库函数----字符串转换、字符测试、及内存控制
    参考 generate-parentheses
    初步整理数仓知识 2017
    论文学习 数码相机系统
    论文学习:数码相机处理器的结构设计
  • 原文地址:https://www.cnblogs.com/zhangzs000/p/12729534.html
Copyright © 2011-2022 走看看