看到8月份更文挑战活动,想来想去还是继续刷leetcode吧。上次持续了没几天就又被其他各种琐事打算了,希望这次能多刷一些题目吧。继续上一次,今天是第10题。
题目
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '' 的正则表达式匹配。
'.' 匹配任意单个字符
'' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
示例 1:
输入: s = "aa" p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa" p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:s = "ab" p = "."
输出:true
解释:"." 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:s = "aab" p = "cab"
输出:true
解释:因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:s = "mississippi" p = "misisp*."
输出:false
思路
虽然很明显是动态规划的题目,不过一开始自己也没想出来题解,参考了一些网上的题解。
首先理解一下题目,'.'比较好理解,就是匹配任意字符即可。''表示可以匹配0个或者多个,其实这里有个隐含的条件,就是''是不能单独出现的,必须跟前面一个字符配对出现才有意义。
理解到了这一点,我们尝试总结状态转义方程就容易了。
首先定义状态dp[i][j]为字符串s的前i个字符和字符串p的前j个字符的匹配结果,注意,这里的i和j不直接是字符串的下标,字符串的第i个字符的下标其实是i+1。那为什么不直接定义dp[i][j]为字符串s的前i-1个字符和字符串p的前j-1个字符的匹配结果呢,这样不是更加符合程序员的理解?原因是这样的话,无法表达出字符串为空的情况,因为下标为0也就是第一个字符。
有了状态,接下来就是状态转移方程:
情况1:p[j-1] != '*'
子情况1:s[i-1] == p[j-1] 那么 dp[i][j] = dp[i-1][j-1]
子情况2:s[i-1] != p[j-1] 那么 dp[i][j] = false
情况2:p[j-1] == '*'
这种情况下,需要把p[j-2]p[j-1]看成1个整体
因为*可以代表匹配0次,所以至少有 dp[i][j] = dp[i][j-2]
如果不匹配0次,那就要看s[i-1] 是否等于 p[j-2]了;如果不相等,就代表无法匹配1次或者更多;如果相等,用掉1次匹配后,p[j-2]p[j-1]这个整体还是可以继续使用的,因为匹配1-n次其实都是一样的。
Java版本代码
class Solution {
public boolean isMatch(String s, String p) {
int slen = s.length();
int plen = p.length();
// 默认值是false
boolean dp[][] = new boolean[slen + 1][plen + 1];
dp[0][0] = true;
for (int i = 0; i <= slen; i++) {
for (int j = 1; j <= plen; j++) {
if (p.charAt(j-1) == '*') {
dp[i][j] = dp[i][j-2];
if (i > 0 && matchChar(s.charAt(i-1), p.charAt(j-2))) {
dp[i][j] = dp[i][j] || dp[i-1][j];
}
} else {
if (i > 0 && matchChar(s.charAt(i-1), p.charAt(j-1))) {
dp[i][j] = dp[i-1][j-1];
}
}
}
}
return dp[slen][plen];
}
private static boolean matchChar(char temp, char c) {
if (c == '.') {
return true;
}
if (temp == c) {
return true;
}
return false;
}
}