题目
给你一个字符串 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 :
-
如果 p_j 是一个小写字母,那么 s_i 必须等于 p_j,否则无法匹配,即 dp[i][j] = false。
-
如果 p_j 是字符 '.',那么它可以匹配任何一个字符,因此状态转移方程为 dp[i][j] = dp[i-1][j-1]。
-
如果 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的前一个匹配的情况。