掘金团队号上线,助你 Offer 临门! 点击 查看大厂春招职位
一、题目描述:
给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
- s 可能为空,且只包含从 a-z 的小写字母。
- p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。
示例 3:
输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
示例 4:
输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".
示例 5:
输入:
s = "acdcb"
p = "a*c?b"
输出: false
题目地址:leetcode-cn.com/problems/wi…
二、思路分析:
与第10题 Regular Expression Matching 很类似。
一开始的时候也是使用了第10题的思路,递归。如果当前字符匹配成功,最终结果是之后的字符是否匹配成功。
但是超时了。只能改为动态规划了。
定义一个二维的状态数组 boolean dpArray[s.length()+1][p.length()+1]
其中 dp[i][j] 表示:s的前i个字符与p的前j个字符是否匹配。
我们定义 dpArray[0][0] = true,表示 s = "",p = "" 是匹配成功的。
定义状态转换方程:假设我们已知 dpArray[0...i-1][0...j-1] 的值,那么可以推导出dpArray[i][j]的值
- 当
p[j] = "*"时有3种情况:
1. p[j] = "*"
1.1 让"*"匹配 0 个 字符。
那么 dpArray[i][j] = dpArray[i][j-1]
解释一下:如果 s[i] 和 p[j-1] 匹配,那么 s[i] 和 p[j] 也匹配
s = abcd p = ab*cd i = 1,j = 2
dpArray[1][2] = dpArray[1][1]
1.2 让"*" 匹配1个字符
那么 dpArray[i][j] = dpArray[i-1][j-1]
解释一下:如果 s[i-1] 和 p[j-1] 匹配,那么 s[i] 和 p[j] 也匹配
s = abcd p = ab*d i = 2,j = 2
dpArray[2][2] = dpArray[1][1]
1.3 让"*" 匹配N个字符
那么 dpArray[i][j] = dpArray[i-1][j]
解释一下:如果 s[i-1] 和 p[j] 匹配,那么 s[i] 和 p[j] 也匹配
s = abcd p = a*d i = 2,j = 2
dpArray[2][1] = dpArray[1][1]
- 当
p[j] = "?"
"?"只能匹配一个字符,所以
dpArray[i][j] = dpArray[i-1][j-1]
解释一下:如果 s[i-1] 和 p[j-1] 匹配,那么 s[i] 和 p[j] 也匹配
- 当
p[j] = "a-z"
dpArray[i][j] = dpArray[i-1][j-1] && s[i] == p[j]
解释一下:如果 s[i-1] 和 p[j-1] 匹配 并且 s[i] == p[j],那么 s[i] 和 p[j] 也匹配
如果不太理解可以手动实现这个过程:以 s = abcd ,p = a*d 为例
"" a * d
"" T F F F
a F T T F
b F F T F
c F F T F
d F F T T
多手写几次这个数组,慢慢就理解了。
最后 dp[s.length()][p.length()]就是最终答案
三、AC 代码:
private static boolean dfs(String s, String p, int sIndex, int pIndex) {
if (pIndex == p.length()) {
//正则以 * 号结尾
if ('*' == p.charAt(pIndex - 1)) {
return true;
}
//正则结束
return sIndex == s.length();
}
//解决s结束,而正则没有结束 ,s = abcd p = a*
if (sIndex == s.length()) {
for (int i = pIndex; i < p.length(); i++) {
if ('*' != p.charAt(i)) {
return false;
}
}
return true;
}
if ('*' == p.charAt(pIndex)) {
boolean flag = false;
for (int i = sIndex; i < s.length(); i++) {
flag = dfs(s, p, i, pIndex + 1);
if (flag) {
break;
}
}
return flag;
}
if ('?' == p.charAt(pIndex)) {
return dfs(s, p, sIndex + 1, pIndex + 1);
}
if (s.charAt(sIndex) == p.charAt(pIndex)) {
return dfs(s, p, sIndex + 1, pIndex + 1);
} else {
return false;
}
}
public static boolean isMatch(String s, String p) {
boolean[][] dpArray = new boolean[s.length() + 1][p.length() + 1];
dpArray[0][0] = true;
//解决以 * 号开头的问题 例如 s = abc,p = *****c
for (int i = 0; i < p.length(); i++) {
if ('*' == p.charAt(i)) {
dpArray[0][i + 1] = true;
} else {
break;
}
}
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j < p.length(); j++) {
if ('*' == p.charAt(j)) {
dpArray[i + 1][j + 1] = dpArray[i][j] || dpArray[i][j + 1] || dpArray[i + 1][j];
} else if ('?' == p.charAt(j)) {
dpArray[i + 1][j + 1] = dpArray[i][j];
} else {
dpArray[i + 1][j + 1] = (s.charAt(i) == p.charAt(j)) && dpArray[i][j];
}
}
}
return dpArray[s.length()][p.length()];
}
四、总结:
我觉得这个是最容易理解的解法了,如果要优化的话,可以使用贪心算法来处理 * 号匹配的问题。甚至更优的AC自动机。但是理解难度高了不少。