目录
一、问题描述
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持 '.'
和 '*'
的正则表达式匹配。
'.'
匹配任意单个字符'*'
匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s
的,而不是部分字符串。
二、解题思路
这是一个经典的动态规划问题。需要匹配字符串 s
和模式 p
,其中模式包含特殊字符 .
和 *
:
.
可以匹配任何单个字符。*
可以匹配前面的字符 0 次或多次。
我们可以通过动态规划来解决这个问题。定义 dp[i][j]
为布尔值,表示 s[0...i-1]
是否与 p[0...j-1]
匹配。最终结果保存在 dp[m][n]
中,m
和 n
分别是字符串 s
和模式 p
的长度。
动态规划转移方程:
-
当
p[j-1] == s[i-1]
或p[j-1] == '.'
时:dp[i][j] = dp[i-1][j-1]
。表示如果当前字符匹配,取决于前面的状态。
-
当
p[j-1] == '*'
时:-
需要考虑
*
的两种可能性:-
*
匹配 0 个字符:dp[i][j] = dp[i][j-2]
,跳过前一个字符和*
。 -
*
匹配一个或多个字符:需要当前字符s[i-1]
和p[j-2]
匹配,并且dp[i-1][j] = true
,即dp[i][j] = dp[i-1][j]
。
-
-
-
边界条件:
dp[0][0] = true
:表示空字符串和空模式匹配。- 当
p
中包含*
时,dp[0][j]
可能为true
,表示空字符串可以匹配带*
的模式。
三、代码
public class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(); // 字符串 s 的长度
int n = p.length(); // 模式 p 的长度
// dp[i][j] 表示 s[0...i-1] 是否匹配 p[0...j-1]
boolean[][] dp = new boolean[m + 1][n + 1];
// 1. 初始化:空字符串和空模式匹配
dp[0][0] = true;
// 2. 初始化 dp[0][j]:空字符串与模式匹配的情况
// 模式 p 的形式可能是 a*、a*b*、a*b*c*... 这样才可能匹配空字符串
for (int j = 2; j <= n; j += 2) {
if (p.charAt(j - 1) == '*') {
dp[0][j] = dp[0][j - 2];
}
}
// 3. 填充 dp 表
for (int i = 1; i <= m; i++) { // 遍历字符串 s
for (int j = 1; j <= n; j++) { // 遍历模式 p
// 情况 1:当前字符直接匹配 或者 模式为 '.'
if (p.charAt(j - 1) == s.charAt(i - 1) || p.charAt(j - 1) == '.') {
dp[i][j] = dp[i - 1][j - 1];
}
// 情况 2:模式字符为 '*'
else if (p.charAt(j - 1) == '*') {
// * 号匹配 0 次,跳过前一个字符和 *
dp[i][j] = dp[i][j - 2];
// * 号匹配 1 次或多次,当前字符和 p[j-2] 匹配
if (p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(j - 2) == '.') {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
}
}
}
// 最终结果在 dp[m][n]
return dp[m][n];
}
}
四、复杂度分析
- 时间复杂度:O(m * n),其中 m 是字符串
s
的长度,n 是模式p
的长度。我们需要遍历整个dp
表。 - 空间复杂度:O(m * n),我们使用了一个大小为
(m+1) x (n+1)
的dp
表。