@author stormma
@date 2018/03/24
生命不息,奋斗不止
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).
The function prototype should be:
bool isMatch(const char *s, const char *p)
Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false
首先我们选取状态, 用boolean[][] dp
数组来标记s中i个字符串是否匹配于p中j个字符串, 当然如果匹配则为True
, else False
。
下面我们来分析一下状态是怎么变化的。
且看图:
通过上面的状态转移图, 我们很容易可以得到:
1. 边界初始化, 当si = 0;
(即s字符串取0个字符), ps 0 -> p.length()
的初始化为: 如果此时的p[pi]
这个字符是*
的话,
那么它的值应该是上一个的值, 即dp[0][pi] = dp[0][pi - 1]
, 如果此时p[pi] != '*'
呢, 那么肯定是False
. 当pi = 0;
,
初始化dp[si][0] = False && si >= 1
(p中取出0个字符, s中取出任意个(>0), 都是不匹配的), dp[0][0] = True
(此时s中取0个字符和p中取0个字符, 无疑是匹配的).
2. 边界初始化完成之后, 我们分析一下其他地方怎么转移. 分为以下两种情况来讨论:
- if p[pi] == '*'
, 无非是从它的左边和上边转移而来的。
- if p[pi] == '?' || p[pi] == s[si]
, 那么它肯定是从它左上角转移而来。
状态转移分析完成之后, 显然答案便是dp[s.length()][p.length()]
/**
* dp解决
* time running time: O(n*m)
* extra space: O(n*m)
*/
static class Solution2 {
public boolean isMatch(String s, String p) {
if (s == null && p == null) return true;
if (s == null || p == null) return false;
// dp[i][j]表示s字符串中前i个和p中前j个字符是否匹配
boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
dp[0][0] = true;
// 初始化边界
for (int i = 1; i <= p.length(); i++) {
dp[0][i] = p.charAt(i - 1) == '*' && dp[0][i - 1];
}
for (int i = 1; i <= s.length(); i++) {
for (int j = 1; j <= p.length(); j++) {
if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else if (p.charAt(j - 1) == '*') {
// ab abcd
// ab* ab*
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
}
}
}
return dp[s.length()][p.length()];
}
}
我们可以用一个指针来记录*
出现的位置, 再用两个指针标记s
和p
的位置: sp
和pp
指针, 再定义一个
辅助变量match
, 分析如下:
1. 如果此时s[sp] == p[pp] || p[pp] == '?'
, 那么我们只需让sp
和pp
指针走一步即可。
2. 如果此时的p[pp] == '*'
, 我们用star
来记录一下此时*
出现的位置, 用辅助变量match
来记录一下
此时的s
走到了哪个地方, 然后让pp
再走一步。
3. 如果此时star
存在有效的值, 并且都不满足以上两种情况, 那么我们让pp
回到上一次出现*
的下一个位置, 让p
回到上次出现*
的时候, s
的位置的下一个位置。
为什么这样做呢? 我们来看一个例子:
s = bbaec
p = *c
sp = 0, pp = 0, match = 0, star = pp = 0, match = sp = 0 => pp++ => pp = 1
sp = 0, pp = 1, match = 0, star = 0; 此时b != c :
然后s继续走: pp = star + 1 = 1, match++ => match = 1, sp = 1
sp = 1, pp = 1, match = 1, star = 0, 此时 b != c:
s继续走: pp = star + 1 = 1, match++ => match = 2, sp = 2
sp = 2, pp = 1, match = 2, star = 0, 此时 a != c:
s继续走: pp = star + 1 = 1, match++ => match = 3, sp = 3
sp = 3, pp = 1, match = 3, star = 0, 此时 e != c:
s继续走: pp = star + 1 = 1, match++ => match = 4, sp = 4
sp = 4, pp = 1, match = 4, star = 0, 此时 c == c:
sp++ => sp = 5, pp++ => pp = 2
退出循环, 说明`s`走到了头, 但是不确定`p`是否走到了头, 外加一个判断p的循环即可解决。
说到底, 如果遇到不符合字符串不相等并且不存在*
的时候, 就回退到上一个star出现的地方, 然后跳过可能被*
匹配的字符。
static class Solution1 {
/**
* time running: O(n)
* bbarc
* *c
* @param s
* @param p
* @return
*/
public boolean isMatch(String s, String p) {
if (s == null && p == null) return true;
if (s == null || p == null) return false;
int sp = 0, pp = 0, star = -1, match = 0;
while (sp < s.length()) {
if (pp < p.length() && (s.charAt(sp) == p.charAt(pp) || p.charAt(pp) == '?')) {
sp++;
pp++;
} else if (pp < p.length() && p.charAt(pp) == '*') {
star = pp;
match = sp;
pp++;
} else if (star != -1) {
pp = star + 1;
match++;
sp = match;
} else {
return false;
}
}
while (pp < p.length() && p.charAt(pp) == '*') pp++;
return pp == p.length();
}
}
Implement regular expression matching with support for '.' and '*'.
'.' Matches any single character.
'*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
The function prototype should be:
bool isMatch(const char *s, const char *p)
Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true
此题和上一个题很类似, 我们还是采用dp
来解决, 状态选择情况一样, boolean [][] dp = new boolean[s.length() + 1][p.length() + 1]
状态转移也很简单, 在这就不画图分析了, 直接给出AC代码。
static class Solution {
public boolean isMatch(String s, String p) {
if (p.isEmpty()) {
return s.isEmpty();
}
boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
dp[0][0] = true;
for (int j = 1; j <= p.length(); j++) {
if (p.charAt(j - 1) == '*') {
dp[0][j] = dp[0][j - 2];
}
}
for (int i = 1; i <= s.length(); i++) {
for (int j = 1; j <= p.length(); j++) {
if (p.charAt(j - 1) == '.' || s.charAt(i - 1) == p.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else if (p.charAt(j - 1) == '*') {
if (p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(j - 2) == '.') {
dp[i][j] = dp[i][j - 2] || dp[i - 1][j];
} else {
dp[i][j] = dp[i][j - 2];
}
}
}
}
return dp[s.length()][p.length()];
}
}
博客来自于我的个人博客转载请注明出处