1、题目描述
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持 '.'
和 '*'
的正则表达式匹配。
'.'
匹配任意单个字符'*'
匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s
的,而不是部分字符串。
示例 1:
输入:s = "aa", p = "a" 输出:false 解释:"a" 无法匹配 "aa" 整个字符串。
2、题解
分析过程:
首先,我们需要明确动态规划的基本思想:将问题分解为子问题,并存储子问题的解以避免重复计算。在这个问题中,我们关注的是字符串str
和模式串pattern
的匹配问题。
①定义dp[i][j]
为:当字符串str
的前i
个字符与模式串pattern
的前j
个字符是否匹配。
注意,dp[i][j]的下标对应字符串str的下标为i-1,对应模式串pattern的下标为j-1。
②初始化:dp[0][0] = true
,表示空字符串与空模式匹配
③处理str为空的情况:若str为空,要使得匹配成功,则pettern的右端必为*,这样才能将前面的字符消去,也就是这种形式: ....a*,对应状态转移方程:dp[0][i] = dp[0][i - 2]
④状态转移方程:
当前字符不为‘*’,若当前字符不匹配,则匹配失败;当前字符匹配成功的情况有两种分别为:str.charAt(i - 1) == pattern.charAt(j - 1)和pattern.charAt(j - 1) == '.',对应状态转移方程为:dp[i][j] = dp[i - 1][j - 1]。
当前字符为‘*’,需要考虑两种情况:str当前字符(对应i-1)与pattern前一个字符(对应j-2)是否匹配
(i)若匹配,即str.charAt(i - 1) == pattern.charAt(j - 2) || pattern.charAt(j - 2) == '.'
当前字符与前一个字符或'.'匹配,可以选择匹配0次、1次或多次,对应状态转移方程为:
匹配0次:dp[i][j] = dp[i][j - 2]
匹配1次:dp[i][j] = dp[i - 1][j - 2]
匹配多次:dp[i][j] = dp[i - 1][j]
这里解释一下匹配多次的情况,个人是这样理解的,可以参考:
当pattern匹配N次,最右端的a与str当前字符a匹配(可以看作消去一个a),则还剩下N-1个a,仍然可以看作a*形式,所以转移后pattern下标不变,仍考虑前j个字符,即dp[i][j] = dp[i - 1][j]
(ii)若不匹配,则需要*将前一个字符消去,对应状态转移方程:dp[i][j] = dp[i][j - 2]
通过以上分析,可以得到完整的动态规划状态转移方程。通过填充dp数组,最终可以确定字符串`str`是否与模式串`pattern`匹配。
使用动态规划求解,代码如下:
class Solution {
// isMatch方法,判断str是否与pattern匹配
public boolean isMatch(String str, String pattern) {
// 获取str和pattern的长度
int m = str.length();
int n = pattern.length();
// 创建一个二维布尔数组dp,用于存储中间结果
boolean[][] dp = new boolean[m + 1][n + 1];
// 初始化,空字符串与空模式串匹配
dp[0][0] = true;
// 处理str为空的情况
for (int i = 1; i < n + 1; i++) {
if (pattern.charAt(i - 1) == '*')
dp[0][i] = dp[0][i - 2]; // 则不匹配,与前一个字符的状态相同
}
// 使用双重循环遍历str和pattern的所有字符
for (int i = 1; i < m + 1; i++) {
for (int j = 1; j < n + 1; j++) {
// 当前字符不为'*',如果当前字符相等或者模式串的当前字符是'.'
if (str.charAt(i - 1) == pattern.charAt(j - 1) || pattern.charAt(j - 1) == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (pattern.charAt(j - 1) == '*') {
// 前一个相等,可以复制0次、1次、>=2次,三种情况之一为真则dp[i][j]为真
if (str.charAt(i - 1) == pattern.charAt(j - 2) || pattern.charAt(j - 2) == '.') {
dp[i][j] = dp[i][j - 2] || dp[i - 1][j - 2] || dp[i - 1][j];
} else // 前一个不相等,只考虑'*'匹配0次的情况
dp[i][j] = dp[i][j - 2];
}
}
}
// 返回dp数组的最后一个元素,即str和pattern的匹配结果
return dp[m][n];
}
}
如果仍然理解困难,可参考以下链接手画图解
. - 力扣(LeetCode)