正则表达式匹配

246 阅读3分钟

题目

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

参考

正则表达式匹配

题解

class Solution {
    fun isMatch(s: String, p: String): Boolean {
        //1.初始化

        val m = s.length
        val n = p.length

        //2.定义转义数组
        val dp = Array(m + 1) { Array(n + 1) { false } }
        dp[0][0] = true
        //3.定义转义方程
        for (i in 0..m) {
            for (j in 1..n) {
                //.的情况
                if(p[j-1] == '.') {
                    dp[i][j] = i> 0 && dp[i-1][j-1]
                }
                //*的情况
                else if(p[j-1] == '*') {
                    dp[i][j] = dp[i][j-2] ||
                            //1个字符
                            i> 0 && (p[j-2] == '.' || p[j-2] == s[i-1]) && dp[i-1][j-2] ||
                            //多个字符
                            i> 0 && (p[j-2] == '.' || p[j-2] == s[i-1]) && dp[i-1][j]
                }
                //普通字符的情况
                else {
                    dp[i][j] = i > 0 && dp[i-1][j-1] && p[j-1] == s[i-1]
                }
            }

        }
        //4.返回
        return dp[m][n]

    }

转义方程定义

为了实现正则表达式匹配,我们可以使用动态规划。我们可以用 dp[i][j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否能够匹配。在状态转移时,我们考虑模式 p 的第 j 个字符 p_j :

  1. 如果 p_j 是一个小写字母,那么 s_i 必须等于 p_j,否则无法匹配,即 dp[i][j] = false。

  2. 如果 p_j 是字符 '.',那么它可以匹配任何一个字符,因此状态转移方程为 dp[i][j] = dp[i-1][j-1]。

  3. 如果 p_j 是字符 ' ',那么它可以匹配任何数量的字符,包括零个字符。因此我们需要分别考虑 ' ' 匹配零个字符、匹配一个字符、匹配多个字符的情况。

    • 匹配零个字符:dp[i][j] = dp[i][j-2]
    • 匹配一个字符:dp[i][j] = dp[i-1][j-2] (如果 s_i 等于 p_{j-1} 或者 p_{j-1} 是字符 '.')。
    • 匹配多个字符:dp[i][j] = dp[i-1][j] (如果 s_i 等于 p_{j-1} 或者 p_{j-1} 是字符 '.')。

最终的状态就是 dp[len(s)][len(p)],其中 len(s) 表示字符串 s 的长度,len(p) 表示模式 p 的长度。

问题

为什么j是从1开始

在这个问题中,我们使用的是一个二维的动态规划数组 dp,其中 dp[i][j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否能够匹配。因为 dp 数组是一个二维数组,所以我们需要使用两个循环来遍历它。

在这个算法中,我们选择使用 0-based indexing(从 0 开始编号)来表示字符在字符串中的位置。因此,字符串 s 的第一个字符实际上是 s[0],第二个字符是 s[1],以此类推。同样地,模式 p 的第一个字符是 p[0],第二个字符是 p[1],以此类推。

在第一个循环中,我们遍历字符串 s 的所有前缀,从空字符串开始,到整个字符串 s。因此,我们将循环变量 i 初始化为 0,表示空字符串。因为我们需要在 dp[0][0] 上设置初始值,所以我们需要将 dp[0][0] 设为 true

在第二个循环中,我们遍历模式 p 的所有前缀,从空模式开始,到整个模式 p。因此,我们将循环变量 j 初始化为 1,表示空模式已经匹配空字符串。注意,因为我们需要在 dp[0][0] 上设置初始值,所以我们将循环变量 j 从 1 开始。

综上所述,循环变量 i 从 0 开始,循环变量 j 从 1 开始,是因为我们使用了 0-based indexing 来表示字符在字符串中的位置,并且我们需要在 dp[0][0] 上设置初始值。

匹配多个字符串为啥使用dp[i-1][j]

可以考虑为当前的模式下匹配多个字符串,j不变,i的前一个匹配的情况。