这题是《剑指Offer》的原题,在LeetCode上属于hard难度,虽然面试可能不会考到这种题目,但能够很好地训练自己的思维~
书中的解法是递归,但因为没有剪掉重复问题的结果,导致时间开销巨大,LeetCode上测试大概3秒,相比之下,动态规划只用了3ms,这也足以看出动态规划的剪枝对减少运行时间的作用是巨大的。
首先分析这道题为什么是动态规划:例如,s = "abc" p = "abc"
,我们会从前向后逐个检查s和p中对应的位置是不是相等,如果相等就检查下一个位置,所以大问题的结果可以看成去掉结尾字符的子串是否匹配和结尾是否匹配的组合,因此,这是个动态规划问题。
既然是动态规划问题,那就要确定状态,因为*
的存在,s和p能够在长度不相等的情况下匹配,所以,要用一个二维数组记录状态:dp[i][j]
表示s的前i个字符能否匹配上p的前j个。
选定状态后,要先确定最小子问题的结果,也就是初值,有了初值,才能自下向上逐步计算更大子问题的结果。初值有以下三种情况:
-
dp[0][0] = true
,s和p的前0个元素一定是可以匹配的,毕竟两个空集相等嘛~ -
dp[0][j]
:比如,s为空,p = "a*"
时能匹配,即dp[0][2] == true
,这是因为p[1] == '*'
,一个星号让它前面的字符消失,同时dp[0][0] == true
,所以dp[0][2] == true
。进行逻辑抽象后表述:只有当dp[0][j-2] == true
且p[j-1] == '*'
时,dp[0][j] = true
; -
dp[i][0]
:s不为空,p为空,这种情况无论如何都不能匹配,所以全是false。
int row = s.length();
int col = p.length();
boolean[][] dp = new boolean[row+1][col+1];
dp[0][0] = true;
for(int j = 1; j <= col; j++){
if(p.charAt(j-1) == '*' && j >= 2 && dp[0][j-2]){
dp[0][j] = true;
}
}
接下来就到了最难的状态转移方程环节,由于*
的存在会让问题变得复杂,我们按照p[j-1]
是否为*
来分类讨论:
-
p[j-1] != '*'
1.1p[j-1] == s[i-1]
,两个字符串的最后一位相等,则结果要由前面的子串判断,即:dp[i][j] = dp[i-1][j-1]
;
1.2p[j-1] == '.'
,因为.
可以匹配任一字符,和情况1.1是等价的,因此,结果依然是dp[i][j] = dp[i-1][j-1]
;
1.3p[j-1] != s[i-1]
,直接让dp[i][j] = false
,不解释~ -
p[j-1] == '*'
2.1p[j-2] == s[i-1] || p[j-2] == '.'
,即*
号前的字符和s[i-1]
相等(.
也可以认为是相等),还要再分为三种情况讨论,以下三种情况只要有一个符合就可以:
2.1.1*
不匹配字符:s = "ab" p = "abb*"
,s的前两位已经和p的前两位匹配了,*
只需要让前面的字符消失即可,即dp[i][j] = dp[i][j-2]
;
2.1.2*
匹配1个字符:s = "aab" p = "aab*"
,此时的b*
需要转换成b
,因此,dp[i][j] = dp[i][j-1]
;
2.1.3*
匹配多个字符:s = "aabb" p = "aab*"
,这时的b*
需要转换成bb
,也相当于在s末尾去掉一个b,即dp[i][j] = dp[i-1][j]
;
2.2p[j-2] != s[i-1]
,也就是s = "a" p = "b*"
这种情况,只能去掉b*
,因此,dp[i][j] = dp[i][j-2]
。
最终的代码:
class Solution {
public boolean isMatch(String s, String p) {
if(s == null || p == null) return false;
int row = s.length();
int col = p.length();
boolean[][] dp = new boolean[row+1][col+1];
//状态矩阵初始化
dp[0][0] = true;
for(int j = 1; j <= col; j++){
if(p.charAt(j-1) == '*' && j >= 2 && dp[0][j-2]){
dp[0][j] = true;
}
}
for(int i = 1; i <= row; i++){
for(int j = 1; j <= col; j++){
//p[j-1] != '*'的情况
if(p.charAt(j-1) != '*'){
if(p.charAt(j-1) == s.charAt(i-1) || p.charAt(j-1) == '.'){
dp[i][j] = dp[i-1][j-1];
}
//p[j-1] == '*'的情况
}else{
//以下的情况都有j-2
if(j >= 2){
if(p.charAt(j-2) == s.charAt(i-1) || p.charAt(j-2) == '.'){
dp[i][j] = dp[i][j-2] || dp[i][j-1] || dp[i-1][j];
}else{
dp[i][j] = dp[i][j-2];
}
}
}
}
}
return dp[row][col];
}
}
最终结果: