美文网首页
字符串的几个问题

字符串的几个问题

作者: 原来哥哥是万家灯火 | 来源:发表于2022-10-04 18:36 被阅读0次

    1.最长公共子序列
    2.最长公共子串
    3.最长回文串
    4.最长回文序列
    5.最长递增序列
    6.最长先增后减序列
    7.(最短)编辑距离
    8.子串匹配问题
    9.通配符问题

    1.最长公共子序列(longest common subsequence, LCS)
    动态规划解法

    dp[i][j]表示字符串a的前i长度的子串a',和b的前j个长度子串的b'的最长公共序列的长度

    状态转移方程
    dp[i][j]= \begin{cases} dp[i-1][j-1]+1\space\space 当a[i-1] == b[i-1]时\\ max(dp[i-1][j],dp[i][j-1])\space\space 当a[i-1] != b[i-1]时 \end{cases}

    func LCS(a string, b string) int {
        dp := make([][]int, len(a)+1)
        for i, _ := range dp {
            dp[i] = make([]int, len(b)+1)
            if i == 0 {
                continue
            }
            for j, _ := range dp[i] {
                if j == 0 {
                    continue
                }
                if a[i-1] == b[j-1] {
                    dp[i][j] = dp[i-1][j-1] + 1
                } else {
                    dp[i][j] = dp[i-1][j]
                    if dp[i][j-1] > dp[i][j] {
                        dp[i][j] = dp[i][j-1]
                    }
                }
            }
        }
    
        return dp[len(a)][len(b)]
    }
    

    复杂度:
    T = O(mn)
    S = O(m
    n)

    2.最长公共子串(longest common substring)

    动态规划解法

    a[0:i]为a前i个长度的子串,b[0:j]为b前j个长度的子串
    dp[i][j]表示a[0:i]b[0:j]的以二者最后一个字符结尾的最长公共子串的长度
    比如a是god,b是goodsdp[3][4]就是2(od的长度),dp[3][5]就是0

    转移方程
    dp[i][j]= \begin{cases} dp[i-1][j-1]+1 \space\space 当a[i-1] == b[i-1]时\\ 0 \space\space 当a[i-1] != b[i-1]时 \end{cases}

    func LCSubstring(a string, b string) int {
        dp := make([][]int, len(a)+1)
        max := 0
        for i, _ := range dp {
            dp[i] = make([]int, len(b)+1)
            if i == 0 {
                continue
            }
            for j, _ := range dp[i] {
                if j == 0 {
                    continue
                }
                if a[i-1] == b[j-1] {
                    dp[i][j] = dp[i-1][j-1] + 1
                } else {
                    dp[i][j] = 0
                }
                if dp[i][j] > max {
                    max = dp[i][j]
                }
            }
        }
    
        return max
    }
    
    暴力解

    遍历a,依次与b的所有字符比较,如果a[i]==b[j],再比较a中a[i]开头和b中b[j]开头的子串。

    func LCSubstring(a string, b string) int {
        max := 0
        for i, _ := range a {
            for j, _ := range b {
                if a[i] != b[j] {
                    continue
                }
                for k := i + 1; k < len(a); k++ {
                    for l := j + 1; l < len(b); l++ {
                        if a[i:k+1] == b[j:l+1] && k+1-i > max {
                            max = k + 1 - i
                        }
                    }
                }
            }
        }
        
        return max
    }
    

    最坏复杂度O(m^2*n^2)

    广义后缀数解

    // 留坑

    3.最长回文串

    暴力解

    遍历每个字符串
    以当前字符a[i]为中心,向两边探测是否两侧字符是否相同
    以当前字符a[i]、a[i+1]为中心,向两边探测是否两侧字符是否相同

    func LPSubstring(a string) int {
        max := 0
        for i := 0; i < len(a); i++ {
            j := 1
            for ; i+j < len(a) && i-j >= 0 && a[i+j] == a[i-j]; j++ {
            }
            if 2*j-1 > max {
                max = 2*j - 1
            }
    
            if i < len(a)-1 && a[i] == a[i+1] {
                k := 1
                for ; i+1+k < len(a) && i-k >= 0 && a[i+1+k] == a[i-k]; k++ {
                }
                if 2*k > max {
                    max = 2 * k
                }
            }
        }
    
        return max
    }
    

    时空复杂度
    时间复杂度,最坏情况是整个字符串都由一个字符组成,每个字符都要向两侧探测到底。
    T_worse = O(n^2)
    S = O(1)

    动态规划解

    长度为1的子串,肯定是回文串
    长度为2的子串,如果两字符相同,则是回文串
    长度超过2的子串,如果首位字符相同,且首位之间也是回文串,则是回文串

    设dp[i][j]表示起点为a[i],终点为a[j]的子串是否为回文串
    转移方程
    dp[i][j]= \begin{cases} 当j==i\space时,true \space\space \\ 当j==i+1时,\space\space 若a[i]==a[j], true。否则false \\ 当j>=i+2时,\space\space 若a[i]==a[j] 且 dp[i+1][j-1],true。否则false \\ \end{cases}

    当子串长度大于2时要依赖前面求解过的结果

    试着推一下。假设有字符串abcdcf
    i=0,j=0,长度为1,true
    i=0,j=1,长度为2,false
    i=0,j=2,长度为3,需要判断ac是否相同,如果相同,要依赖a[1][1],但是a[1][1]不知道
    所以i 递增,j 递增这个顺序推不行

    换个方向:i 递增,j 递减
    i=0,j=5,依赖a[1][4]a[1][4]结果还没有出来,所以也不行

    再换个方向:i 递减,j 递增
    i=5,j=5,长度为1,无依赖
    i=4,j=4,长度为1,无依赖
    i=4,j=5,长度为2,无依赖
    i=3,j=3,长度为1,无依赖
    i=3,j=4,长度为2,无依赖
    i=3,j=5,长度为3,依赖a[4][4]a[4][4]已经出来了,a[3][5]可以推出

    所以i 递减,j 递增这个方向推是可行的

    微信图片_20220929134821.jpg
    func LPSubstring(a string) int {
        dp := make([][]bool, len(a))
        max := 0
        for i := len(dp) - 1; i >= 0; i-- {
            dp[i] = make([]bool, len(a))
            for j := i; j < len(dp[i]); j++ {
                // 长度为1的子串肯定是回文串
                if j == i {
                    dp[i][j] = true
                    continue
                }
                // 长度为2的子串,俩字符相同即为回文串
                if j == i+1 {
                    dp[i][j] = a[i] == a[j]
                    continue
                }
                // 长度超过2的子串,俩字符相同且二者之间满足回文,则为回文串
                dp[i][j] = a[i] == a[j] && dp[i+1][j-1]
                if dp[i][j] && j-i+1 > max {
                    max = j - i + 1
                }
            }
        }
    
        return max
    }
    

    复杂度
    T(n) = O(n^2)
    S(n) = O(n^2)

    原串倒序+求解最长公共子串

    将原来的字符串倒序,再求倒序串和原串的最长公共子串。如原串abadcf,倒序成fcdaba,求二者的最长公共子串。

    马拉车算法(Manacher Algorithm)

    该算法也是动态规划。
    先假设存在的所有回文子串的长度都是奇数,所以对称中心是一个元素。
    dp[i]表示以a[i]为对称中心的最长回文串的长度
    cr分别表示目前发现的右边界最靠右的回文串的对称中心的位置,右边界的位置
    初始c=0, r = 0
    遍历原串,当前位置i一定满足i>=c,如果i<r,说明i处在一个对称的子串中

    19f420a79ca4d4a9aaaac0516341cee.jpg
    i关于c的对称点为i',以i'为中心的最长回文串的左边界 Li'
    如果Li' > r'dp[i] = dp[i']
    如果Li' <= r',则说明dp[i]至少 >=r,然后以i为中心,r-i+1为起点距离向两边探测,如果发现在i串的右边界r之外,则更新cr
    这样利用对称性就避免了从距离1开始向两侧探测。

    如果i=r,没有捷径可走了,只能以i为中心点向外探测,探测完成后更新cr
    如果i>r,只能以i为中心点向外探测,探测完成后更新cr

    预处理原串,插入n+1个分割字符,将abadcf处理成_a_b_a_d_c_f_
    现在所有的回文串长度都是奇数了,如a_a_

    func Manacher(a string) int {
        b := "_"
        for _, n := range a {
            b += string(n) + "_"
        }
    
        c := 0
        r := 0
        max := 0
    
        dp := make([]int, len(b))
        for i, _ := range dp {
            if i < r {
                i2 := 2*c - i // i的对称点
                if i+(dp[i2]-1)/2 < r {
                    dp[i] = dp[i2]
                } else {
                    j := r - i + 1
                    for ; i+j < len(b) && i-j >= 0 && b[i+j] == b[i-j]; j++ {
                    }
    
                    if i+j-1 > r {
                        r = i + j - 1
                        c = i
                    }
    
                    dp[i] = 2*j - 1
                    if dp[i] > max {
                        max = dp[i]
                    }
                }
                continue
            }
            j := 1
            for ; i+j < len(b) && i-j >= 0 && b[i+j] == b[i-j]; j++ {
            }
            if i+j-1 > r {
                r = i + j - 1
                c = i
            }
    
            dp[i] = 2*j - 1
            if dp[i] > max {
                max = dp[i]
            }
        }
    
        return (max - 1) / 2
    }
    

    复杂度
    T(n) = O(2n) = O(n) // 确定每个dp[i]需要O(n),r从0增长到n-1也需要O(n)
    S(n) = O(n)

    4.最长回文序列

    暴力解

    2^n个子序列,检测其是否回文为序列,然后记录下最长的,所以暴力解基本不行。

    原串倒序+求解原串和倒序串的最长公共子序列
    动态规划解

    dp[i][j]表示子串以i为起点,j为终点的子串的最长回文序列。
    转移方程:
    dp[i][j]= \begin{cases} dp[i+1][j-1]+2 \space\space 当a[i]==a[j]\space时\\ max(dp[i][j-1], dp[i+1][j]),\space\space 当a[i]!=a[j]\space时 \\ \end{cases}
    dp[i+1][j-1]表示的是i~j之间的子串的最优值,也就是去掉头尾ij。应该注意判断子串的有效性,如i==j时,[i+1][j-1]这样的子串是不存在的。所以细化一下状态方程可以写成这样:
    dp[i][j]= \begin{cases} j=i时,1\\ j=i+1时, \begin{cases} 若a[i]=a[j],则为2\\ 0 \end{cases} \\ j>= i+2时, \begin{cases} 若a[i]=a[j],则为dp[i+1][j-1]+2\\ max(dp[i][j-1], dp[i+1][j]) \end{cases} \end{cases}

    同样需要i逆推,j正推

    func LPS(a string) int {
        dp := make([][]int, len(a))
        for i := len(dp) - 1; i >= 0; i-- {
            dp[i] = make([]int, len(a))
            for j := i; j < len(a); j++ {
                if j == i {
                    dp[i][j] = 1
                    continue
                }
                if j == i+1 {
                    if a[i] == a[j] {
                        dp[i][j] = 2
                    } else {
                        dp[i][j] = 0
                    }
                    continue
                }
                if a[i] == a[j] {
                    dp[i][j] = dp[i+1][j-1] + 2
                } else {
                    max := dp[i][j-1]
                    if dp[i+1][j] > max {
                        max = dp[i+1][j]
                    }
                    dp[i][j] = max
                }
            }
        }
    
        return dp[0][len(a)-1]
    }
    

    复杂度
    T(n) = O(n^2)
    S(n) = O(n^2)

    5.最长递增序列

    动态规划解

    设dp[i]表示以i结尾的递增子序列的长度。
    求取dp[i]:

    • 首先dp[i]肯定至少为1
    • 遍历0~i-1的元素,如果其中一个元素a[j]的取值小于a[i],那a[j]+1就是a[j]的一个可能的取值,求出这些可能中的最大值即可

    转移方程:
    dp[i] = max(dp[j]+1),0 <= j < i,并且a[j] < a[i]

    func LongestIncreaseSequence(arr []int) int {
        dp := make([]int, len(arr))
        dp[0] = 1
        for i := 1; i < len(arr); i++ {
            dp[i] = dp[i-1]
            for j := 0; j < i; j++ {
                if arr[j] < arr[i] && dp[j]+1 > dp[i] {
                    dp[i] = dp[j] + 1
                }
            }
        }
    
        return dp[len(arr)-1]
    }
    

    复杂度
    T(n) = O(n^2)
    S(n) = O(n)

    二分查找

    比如输入序列为arr := []int{1,5,2,3,9}
    弄一个辅助数组a,初始值是输入序列的第一项
    然后从index=1开始遍历arr,当前值为va的最后一项记作a.tail

    • 如果v>a.tail,则将v添加到a后面,成为a的最后一项
    • 如果v<=a.tail,则利用二分查找定位到a中第一个大于v的元素k,用v替换k

    最终a的长度就是最长序列的长度。这很好理解,a就是被最长序列拓宽的。

    func LongestIncreaseSequence(arr []int) int {
        b := []int{arr[0]}
    
        for i := 1; i < len(arr); i++ {
            if arr[i] > b[len(b)-1] {
                b = append(b, arr[i])
            } else if arr[i] < b[len(b)-1] {
                k := binarySearch(b, arr[i])
                b[k] = arr[i]
            }
        }
        return len(b)
    }
    
    func binarySearch(b []int, v int) int {
        // 找出切片b中,第一个大于v的元素的位置
        left := 0
        right := len(b) - 1
    
        for right > left {
            mid := (left + right) / 2
            if b[mid] < v {
                left = mid + 1
            } else {
                right = mid
            }
        }
    
        if b[left] < v {
            return -1 // 没有找到
        }
    
        return left
    }
    

    复杂度
    T(n) = O(nlogn)
    S(n) = O(n)

    6.最长先增后减序列

    只要这个序列满足先递增,后递减就行,不要求中心点两边的数量一样多

    先求递增+再求递减

    先求出以i结尾的递增序列长度,在求出以i为起点的递减序列长度,两个长度之和-1,就是以i为最高点的序列的先增后减序列的长度。

    牛客网-合唱队问题

    7.(最短)编辑距离

    Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家 Levenshtein 提出的,故又叫 Levenshtein Distance 。
    例如:
    字符串A: abcdefg
    字符串B: abcdef
    通过增加或是删掉字符 ”g” 的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。
    要求:
    给定任意两个字符串,写出一个算法计算它们的编辑距离

    动态规划解

    设有字符串AB
    A[0:i]表示A的 [0,i) 这个区间组成的字符串
    dp[i][j]表示A[0:i]和字符串B[0:j](最短)编辑距离,如dp[1][1]表示a的首字符编辑成b的首字符

    • 当两个子串的末字符相同时,dp[i][j] = dp[i-1][j-1]

    可以用反正法证明一下:
    假设dp[i][j]不等于dp[i-1][j-1]
    也就是A[0:i]B[0:j]存在比dp[i-1][j-1]更优的解KK<dp[i-1][j-1]
    也就是经过K次编辑A[0:i]B[0:j]相同,此时A[0:i-1]B[0:j-1]也相同
    也就是A[0:i-1]B[0:j-1]也存在更优解K<dp[i-1][j-1]
    与题设dp[i-1][j-1]A[0:i-1]B[0:j-1]的最优解矛盾

    • 当两个子串的末字符不相同时


      微信图片_20220930174240.jpg

      A→B有几种可能:
      A'→B',然后a→b
      A'+a → B',然后后面补充一个b
      A'→B'+b,然后删除末尾的a
      此时最优值在三种情况中取最小

    func MinEditDistance(a string, b string) int {
        dp := make([][]int, len(a)+1)
        for i, _ := range dp {
            dp[i] = make([]int, len(b)+1)
            for j, _ := range dp[i] {
                if i == 0 {
                    dp[i][j] = j
                    continue
                }
                if j == 0 {
                    dp[i][j] = i
                    continue
                }
                if a[i-1] == b[j-1] {
                    dp[i][j] = dp[i-1][j-1]
                } else {
                    min := dp[i-1][j-1] + 1
                    if dp[i][j-1]+1 < min {
                        min = dp[i][j-1] + 1
                    }
                    if dp[i-1][j]+1 < min {
                        min = dp[i-1][j] + 1
                    }
                    dp[i][j] = min
                }
            }
        }
        return dp[len(a)][len(b)]
    
    }
    
    

    复杂度
    T(n) = O(m*n)
    S(n) = O(m*n)

    牛客网-计算编辑距离

    8.子串匹配问题

    在主串中寻找子串,如在主串s = "abcde" 中寻找子串sub = "ab",如果找到,返回首次出现位置,如果不存在,返回-1

    暴力解:

    指针i遍历主串,j遍历子串,当失配时,从i+1开始令j=0重新比较

    func SearchSubstring(s string, sub string) int {
        i := 0
        j := 0
        for i < len(s) && j < len(sub) {
            if s[i] == sub[j] {
                i++
                j++
            } else {
                i = i + 1
                j = 0
            }
        }
    
        if j == len(sub) {
            return i - len(sub)
        }
    
        return -1
    }
    

    最坏复杂度,每次都要比较到子串的最后一个字符才发现不匹配,如aaaaaaaaab,aaab。
    T_worse = O((n-m+1)*m) = O((n-m)*m)

    KMP算法(Knuth-Morris-Pratt Algorithm)

    再看一下暴力破解过程,假如主串指针走到i、子串指针走到j时,发生了失配,然后就从i的下一个位置(即i+1)开始,把子串从头到尾重新比较。

    主串: | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |...
    子串: | a | b | c | a | b | ?|...
    假设当 j = 5 时出现失配,说明j前面的部分是和主串吻合的
    主串: | a | b | c | a | b | ? | ? | ? | ? | ? | ? |...
    子串: | a | b | c | a | b | ? |...
    按照暴力算法是从主串当前起始点的下一位开始从头比较
    主串: | a | b | c | a | b | ? | ? | ? | ? | ? | ? |...
    子串: ----| a | b | c | a | b | ? |...

    上帝视角可以发现,i+1这个位置根本不用比较,因为此时的第一位ba就对不上,同样i+2这个位置也不用,因为ca也是第一位就对不上。只有走到i+3,才有可能匹配。
    主串: | a | b | c | a | b | ? | ? | ? | ? | ? | ? |...
    子串: ------------| a | b | c | a | b | ? |...

    这就是kmp的特点,主串指针i不用回头,将子串指针j回头到某个位置即可。(此时前两位a=a,b=b是确定的,所以j也不用直接回到0。)

    j在某个位置发生失配,需要知道j的下一跳应该跳到哪个位置继续,假设这个信息存在一个叫next的表中,并且我们已经得到了next表。此时程序应该这么写:

    func KMP(a string, b string) (allMatches []int) {
        i := 0
        j := 0
    
        next := buildNext(b)
        for i < len(a) {
            if j == len(b) {
                allMatches = append(allMatches, i-len(b))
                j = 0
            }
            if a[i] == b[j] {
                i++
                j++
            } else {
                if j == 0 { // 如果在子串首字符就失配,应将主串指针右移,子串指针不变
                    i++
                } else {
                    j = next[j]
                }
            }
    
        }
    
        return
    }
    

    重点是如何构造next表

    主串: | a | b | - | a | b | ? |...
    子串: | a | b | - | a | b | ? |...

    主串: | a | b | c | - | - | - |a |b| c| ? |...
    子串: | a|b| c| - | - | - | a | b | c | ? |...

    主串: | a | b | c | - | - | - | - | - |a |b| c| ? |...
    子串: | a|b| c| - | - | - | - | - | a | b | c | ? |...

    只要把前面标红的一截和后面标红的一截对齐,这段标红的区域叫做最长公共前后缀j从红色区域的下一个字符开始比较。

    字符串s的前缀的定义是:起点为0,终点在最后一个字符前的子串。比如abc的前缀有两个a、ab,类似地,后缀有c、bc。一个前缀和一个后缀相同,这就是一个公共前后缀。比如abcab最长公共前后缀ab

    next[j]是在j失配时的下一跳位置,也是sub[0:j]的最长公共前后缀长度。比如由sub="abcde"构建的next表,next[2]表示在c发生失配时的下一跳,也表示ab的最长公共前后缀长度。所以有些编码中也把next tableprefix table

    假如sub="aaacd",要求解的内容是:
    next[1] 表j=1失配时的下一跳,也是a的最长公共前后缀,0
    next[2] 表j=2失配时的下一跳,也是aa的最长公共前后缀,1
    next[3] 表j=3失配时的下一跳,也是aaa的最长公共前后缀,2
    next[4] 表j=4失配时的下一跳,也是aaac的最长公共前后缀,0

    构造next表不需要暴力解,可以递推出来。
    首先next[1]肯定是0,当next[k]已经求解出来后,直接比较最长公共前后缀的下一字符是否相同,如果相同,那就可以构成一个更长的公共前后缀。所以next[k+1] = next[k]+1。就像下面的两个问号代表的内容如果相同,就可以形成长度为3公共前后缀。
    | a | b | ? | ... | a | b | ? |...

    如果不同的话,说明无法构成一个更长的公共前后缀,就要尝试是否能构成更短的。

    如下,因为e和b不同,所以无法形成长度为5的公共前后缀

    | a | b | c | a | e | ... | a | b | c | a | b |
    |-----(1)-----|-------|-----(2)-----|

    于是可以在(2)寻找一个后缀,使得它等于(1)一个前缀,然后验证此时是否能为末尾的b找到相同的元素。因为(1)和(2)是相同的,所以相当于在(1)中找最大公共前后缀,直接读取next[4]就知道了,此时next[4]值为1,所以问题又回到了上面的比较公共前后缀的下一字符是否相同。

    • 如果相同,最长公共前后缀+1
    • 如果不同,继续在后缀中找更小的后缀,比较下一字符是否相同...
    • 直到末字符匹配上了或者找到的next[x]是0
    func buildNext(sub string) []int {
        next := make([]int, len(sub))
        for i := 2; i < len(sub); i++ {
            l := next[i-1]
            for l != 0 && sub[l] != sub[i-1] {
                l = next[l]
            }
    
            if sub[l] == sub[i-1] {
                next[i] = l + 1
            } else {
                next[i] = 0
            }
        }
    
        return next
    }
    

    构造next表可以看成在sub中,从i=1开始从搜索自己的前缀

    func buildNext2(sub string) []int {
        next := make([]int, len(sub))
        i := 1
        j := 0
    
        for i < len(sub)-1 {
            if sub[i] == sub[j] {
                next[i+1] = j + 1
                i++
                j++
                continue
            }
            if j == 0 {
                next[i+1] = 0
                i++
            } else {
                j = next[j]
            }
        }
    
        fmt.Println(next)
        return next
    }
    

    9.字符串通配符

    定义:
    ?匹配一个字符
    *匹配任意多个字符,可以为0个,同样
    能被*和?匹配的字符仅由英文字母和数字0到9组成
    给出一个字符串s,一个通配符表达式p,请判断这个字符串是否完全匹配这个通配符。
    比如abb匹配a*b,abc不匹配a*b。

    动态规划解

    dp[i][j]表示s[0:i]是否匹配p[0:j],0<= i <= len(s),0<= j <= len(p)

    递推关系:
    如果p[j-1]是个普通字符:

    • s[i-1]和p[j-1]相同,并且dp[i-1][j-1]为true,则dp[i][j]为true
    • 否则false

    如果p[j-1]是"?"

    • s[i-1]为字母或数字,并且dp[i-1][j-1]为true,则dp[i][j]为true
    • 否则false

    如果p[j-1]是"*",有两种情况可以判定dp[i][j]为true,其余则为false

    • 当dp[i-1][j]为true时,并且最后这个字符是数字或字母。
      (也就是因为ab*可以匹配abc,所以它也能匹配abcdabcde...)

    • 如果dp[i][j-1]为true,此时"*"可以解读为空字符,所以为true,如abab*

    func IsMatch(p string, s string) bool {
        dp := make([][]bool, len(s)+1)
        // dp[i][j]表示s的前i个字符和模式p的前j个字符是否匹配
        for i, _ := range dp {
            dp[i] = make([]bool, len(p)+1)
            for j, _ := range dp[i] {
                if i == 0 {
                    if j == 0 {
                        dp[i][j] = true
                    } else {
                        if p[j-1] == '*' {
                            dp[i][j] = dp[i][j-1]
                        } else {
                            dp[i][j] = false
                        }
                    }
                    continue
                }
    
                if j == 0 {
                    dp[i][j] = false
                    continue
                }
    
                pj := p[j-1]
                si := s[i-1]
    
                if pj == '?' {
                    dp[i][j] = dp[i-1][j-1] && (isLetter(si) || isNumber(si))
                    continue
                }
    
                if pj == '*' {
                    dp[i][j] = (dp[i-1][j] && (isLetter(si) || isNumber(si))) || dp[i][j-1]
                    continue
                }
    
                dp[i][j] = dp[i-1][j-1] && lower(pj) == lower(si)
            }
        }
    
        return dp[len(s)][len(p)]
    }
    
    func isLetter(n byte) bool {
        return (n >= 'a' && n <= 'z') || (n >= 'A' && n <= 'Z')
    }
    
    func isNumber(n byte) bool {
        return n >= '0' && n <= '9'
    }
    
    func lower(n byte) string {
        return strings.ToLower(string(n))
    }
    

    相关文章

      网友评论

          本文标题:字符串的几个问题

          本文链接:https://www.haomeiwen.com/subject/pshhartx.html