一 题目
Given an input string (s
) and a pattern (p
), implement wildcard pattern matching with support for '?'
and '*'
.
'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).
The matching should cover the entire input string (not partial).
Note:
s
could be empty and contains only lowercase lettersa-z
.p
could be empty and contains only lowercase lettersa-z
, and characters like?
or*
.
Example 1:
Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".
Example 2:
Input:
s = "aa"
p = "*"
Output: true
Explanation: '*' matches any sequence.
Example 3:
Input:
s = "cb"
p = "?a"
Output: false
Explanation: '?' matches 'c', but the second letter is 'a', which does not match 'b'.
Example 4:
Input:
s = "adceb"
p = "*a*b"
Output: true
Explanation: The first '*' matches the empty sequence, while the second '*' matches the substring "dce".
Example 5:
Input:
s = "acdcb"
p = "a*c?b"
Output: false
Accepted 195,544 Submissions 836,511
二 分析
求通配符匹配问题,hard级别,跟之前做过的 10. Regular Expression Matching 很类似。主要区别在于星号的作用(10的*前面必须有字母)。它也有两个特殊的字符"?"和"*"。其其中"?"表示匹配一个当前的任意字符,不看星号的话,还是一一对应的,但是对于星号的处理,可以出现在任意位置,可以匹配任意字符串,也是本题的难点所在。
当然* 也有限制,就是受到位置限制: p中有s中不存在的字符,那么一定无法匹配。或者对于p星号之后字母,*不能决定。
因此,要分析不同的情况:
1) * 能全匹配:如s=abbcd,p=a*;
2) 部分匹配:s=abbcd,p=a*cd
规律如下:
1.假设碰到*号时s串的位置是i, p串的位置是j,则将i+1作为*号后续进行比较的起点。
2.从j开始与i+1位置的元素进行比较,如果相同,再按照常规匹配的方式往后面继续比。否则l跳到前面和i+1开始比较那个位置的后面一个。同时p这边的元素也要归位到j+1
2.1 动态规划法
动态规划法适合处理这类字符串的问题,把字符串与子串的关系用动态规划的状态转移实现,还跟上面的10 类似,我们使用一个二维 dp 数组,其中 dp[i][j] 表示 s中前i个字符组成的子串和p中前j个字符组成的子串是否能匹配。大小初始化为 (m+1) x (n+1),加1的原因是要包含 dp[0][0] 的情况(s和p都为空的话也是true).不同的在于星号的处理,这里星号可以出现在任意位置,前面字母不匹配直接就false了,*只是弥补缺失字母的情况。
真正的业务规则:
1 如果s[i]==p[j] or p[j]’==‘?’ 那么dp[i+1][j+1]=dp[i][j]
2 p[j] =='*',那么dp[i][j]= dp[i][j - 1]|| dp[i-1][j];
还有考虑: s=“bcd” p="**cd" 这种连续* 的情况。
public static boolean isMatch(String s, String p) {
if (s == null || p == null) {
return false;
}
if(p.equals("*")){
return true;
}
int m = s.length(), n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int i = 1; i <= n; i++) { // 初始化第一行,p匹配s = ""
if (p.charAt(i-1) == '*' ) {
dp[0][i ] = dp[0][i-1 ];
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
// p[j - 1]不是"*"的情况,单字符匹配
if(s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '?'){
dp[i][j] = dp[i-1][j-1];
}
else if (p.charAt(j - 1) == '*') {
//依赖前面
dp[i][j] = dp[i][j - 1]|| dp[i-1][j];
}
}
}
return dp[m][n];
}
Runtime: 13 ms, faster than 40.46% of Java online submissions for Wildcard Matching.
Memory Usage: 38.4 MB, less than 93.02% of Java online submissions forWildcard Matching.
时间复杂度 O(M*N)
2.2 递归
递归更容易理解,但是递归本身效率不高,需要做优化才能通过测试。
下面的demo只是示意,跑简单的case,提交会报TLE错误。对于这样的case:"abbabaaabbabbaababbabbbbbabbbabbbabaaaaababababbbabababaabbababaabbbbbbaaaabababbbaabbbbaabbbbababababbaabbaababaabbbababababbbbaaabbbbbabaaaabbababbbbaababaabbababbbbbababbbabaaaaaaaabbbbbaabaaababaaaabb"
"**aa*****ba*a*bb**aa*ab****a*aaaaaa***a*aaaa**bbabb*b*b**aaaaaaaaa*a********ba*bbb***a*ba*bb*bb**a*b*bb"
还是把当前两个元素所在s,p的位置分别为i, j。这个demo只是帮助翻译下题目。
//递归
public static boolean isMatch(String s, String p) {
return helper(s,p,0,0);
}
//使用i,j 分别表示s,p的元素索引
public static boolean helper(String s, String p,int i,int j) {
if(i == s.length()){
return j == p.length() || (p.charAt(j) == '*' && helper(s, p, i, j + 1)) ;
}
if (j == p.length() ) return false;
if (p.charAt(j) == '*') {
//跳过连续的'*'
while (j < p.length() && p.charAt(j) == '*') j++;
if (j == p.length()) return true;
while (i < s.length() && !helper(s, p,i, j)) ++i;
return i < s.length();
}
else if (p.charAt(j) == s.charAt(i) || p.charAt(j) == '?') {
return helper(s,p,i+1,j+1);
}
return false;
}
要用递归实现这个题目,需要做深入的优化。这种时间复杂度是指数式阶乘那种。有优化到O(M*N)才行。
2.3 贪心算法
上面的动态规划需要匹配完才知道结果,就是画图取的dp[m][n],有些case可能半路或者一开始就不匹配了,这样就没必要循环到底了。使用贪心算法,利用i,j指针分别标识两个字符串s,p的串首.开始进行匹配若i小于s串的长度,进行 while 循环。
- 若当前两个字符相等,或着p中的字符是问号,则i和j分别加1。
- 若 p[j] 是星号,那么我们要记录星号的位置,lp 赋为j,此时j再自增1,ls 赋为i。
- 若当前 p[j] 不是星号,并且不能跟 p[i] 匹配上,那么此时就要靠星号了.如果没有出现过,直接就false了,如果出现过(就是ls>=0,初始化为-1),则利用*匹配。继续往下匹配,因为之后我们还要检查p串。前面说过*收位置限制,*之后的还要复合规则。
- 处理连续* 的情况,j++;
判断 j 指向串尾时 返回 true。 注意j 不要越界。
//贪心算法
public static boolean isMatch(String s, String p) {
int i = 0, j = 0;
// ls表示最近一个星号匹配到的s的位置
// lp表示最近一个星号的位置
int ls = -1, lp = -1;
while(i< s.length()){
if(j<p.length()&&( s.charAt(i)==p.charAt(j)||p.charAt(j)=='?')){
i++;
j++;
}
else if(j<p.length()&& p.charAt(j)=='*'){
ls =i;
lp = j++;
}
else if(ls>=0){
i =ls++;
j =lp+1;
}else{
return false;
}
}
//跳过连续*
while(j< p.length()&& p.charAt(j)=='*') j++;
return j == p.length();
}
Runtime: 2 ms, faster than 100.00% of Java online submissions for Wildcard Matching.
Memory Usage: 37.8 MB, less than 95.35% of Java online submissions forWildcard Matching.
时间复杂度:O(M*N)
字符串的题目,看似简单,实则复杂。而且适合用不同的方法求解,很考验人。本次的递归还没做出来。