[toc]
字符串匹配问题小结
刷Leetcode时,发现有两个字符串匹配问题很巧妙,所以记录一下
正则表达式匹配
问题描述
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
1 2
| '.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素
|
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
示例 1:
1 2 3 4 5
| 输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。
|
示例 2:
1 2 3 4 5
| 输入: s = "aa" p = "a*" 输出: true 解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
|
示例 3:
1 2 3 4 5
| 输入: s = "ab" p = ".*" 输出: true 解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
|
示例 4:
1 2 3 4 5
| 输入: s = "aab" p = "c*a*b" 输出: true 解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
|
示例 5:
1 2 3 4
| 输入: s = "mississippi" p = "mis*is*p*." 输出: false
|
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/regular-expression-matching
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
回溯思想:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class Solution { char[] str; char[] pattern; public boolean isMatch(String s, String p) { if (s==null||p==null) { return false; } this.str = s.toCharArray(); this.pattern = p.toCharArray(); return matchCore(0,0); }
boolean matchCore(int sStart,int pStart){ if (sStart==str.length && pStart==pattern.length) return true; if (sStart!=str.length && pStart==pattern.length) return false;
if (pStart<pattern.length-1 && pattern[pStart+1]=='*') { if (sStart!=str.length && (pattern[pStart]==str[sStart]||pattern[pStart]=='.')) { return matchCore(sStart+1, pStart+2)||matchCore(sStart+1, pStart) || matchCore(sStart, pStart+2); }else{ return matchCore(sStart, pStart+2); } }
if (sStart!=str.length && (pattern[pStart]==str[sStart]||(pattern[pStart]=='.' ))) return matchCore(sStart+1, pStart+1);
return false;
} }
|
回溯比较好理解,代码也比较好实现,总结来说就:
动态规划:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Solution { public boolean isMatch(String s, String p) { int m = s.length(); int n = p.length(); boolean[][] f = new boolean[m + 1][n + 1]; f[0][0] = true; for (int i = 0; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (p.charAt(j - 1) == '*') { f[i][j] = f[i][j - 2]; if (matches(s, p, i, j - 1)) { f[i][j] = f[i][j] || f[i - 1][j]; } } else { if (matches(s, p, i, j)) { f[i][j] = f[i - 1][j - 1]; } } } } return f[m][n]; }
public boolean matches(String s, String p, int i, int j) { if (i == 0) { return false; } if (p.charAt(j - 1) == '.') { return true; } return s.charAt(i - 1) == p.charAt(j - 1); } }
|
动态规划主要是要想出子问题以及动态转移方程。我们可以先画个表格,显示一下子问题的结构
|
c |
* |
a |
* |
b |
| a |
F |
F |
T |
T |
F |
| a |
|
|
T |
T |
F |
| b |
|
|
|
|
T |
转移大概这样:

状态转移方程的思路:
在上面表格的帮助下,我们可以确定dp[i][j]的含义, dp[i][j]表示 s 串的前 i个字符是否能被 p 的前 j 个字符匹配。所以有下列的式子:
p[j] == s[i] : dp[i][j] = dp[i-1][j-1]
p[j] == "." : dp[i][j] = dp[i-1][j-1]
p[j] ==" * ":
p[j-1] != s[i] : dp[i][j] = dp[i][j-2],即'*'前一个字符和s串的第i个字符不匹配时,我们将该字符作为空处理,所以dp[i][j] = dp[i][j-2],比如(ab, abc * )这种情况
p[j-1] == s[i] or p[j-1] == ".",,即'*'前一个字符和s串的第i个字符匹配时,
dp[i][j] = dp[i-1][j] // 多个字符匹配的情况,这种思维上感觉最难理解,我个人是理解为(aaa,a*)这种情况,画个表格可以看出一些效果,类似于当s第一个a和q的第一个a匹配后,无论后面多几个a都不影响,只需要关注上一个(i-1)是否匹配。
dp[i][j] = dp[i][j-1] // 单个字符匹配的情况,这种情况其实是多余的,但是对应着上面回溯的做法,写出来比较好理解。为什么说多余?因为单个字符匹配的情况可以说是多个字符匹配的情况的特例,虽然数学上暂时我还不会证明,但是可以举出(aa,a*)这个例子。
dp[i][j] = dp[i][j-2] // 没有匹配的情况
进行归纳起来就是
- 如果
p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];
- 如果
p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1];
- 如果
p.charAt(j) == ' * ':
- 如果
p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty
- 如果
p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == '.':
dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a
or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty
具体可以参考:
通配符匹配
问题描述
给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。
1 2
| '?' 可以匹配任何单个字符。 '*' 可以匹配任意字符串(包括空字符串)。
|
两个字符串完全匹配才算匹配成功。
说明:
- s 可能为空,且只包含从 a-z 的小写字母。
- p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
示例 1:
1 2 3 4 5
| 输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。
|
示例 2:
1 2 3 4 5
| 输入: s = "aa" p = "*" 输出: true 解释: '*' 可以匹配任意字符串。
|
示例 3:
1 2 3 4 5
| 输入: s = "cb" p = "?a" 输出: false 解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
|
示例 4:
1 2 3 4 5
| 输入: s = "adceb" p = "*a*b" 输出: true 解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".
|
示例 5:
1 2 3 4
| 输入: s = "acdcb" p = "a*c?b" 输出: false
|
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/wildcard-matching
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
回溯思想
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Solution { char[] str; char[] pattern; public boolean isMatch(String s, String p) { if (s==null||p==null) { return false; } this.str = s.toCharArray(); this.pattern = p.toCharArray(); return matchCore(0,0); }
boolean matchCore(int sStart,int pStart){ if (sStart==str.length && pStart==pattern.length) return true; if (sStart!=str.length && pStart==pattern.length) return false;
if(sStart<str.length && pattern[pStart]=='*'){ return matchCore(sStart, pStart+1) || matchCore(sStart+1, pStart+1) || matchCore(sStart+1, pStart); }else if (sStart<str.length && pattern[pStart]==str[sStart]||(pattern[pStart]=='?')) { return matchCore(sStart+1, pStart+1); }else if (sStart==str.length && pattern[pStart]=='*') { return matchCore(sStart, pStart+1); }
return false; } }
|
思路和上一题类似
动态规划:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Solution {
public boolean isMatch(String s, String p) { int m = s.length(); int n = p.length(); boolean[][] dp = new boolean[m + 1][n + 1]; dp[0][0] = true; for (int i = 1; i <=n; i++) { if (p.charAt(i - 1) == '*') { dp[0][i] = true; } else { break; } }
for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (p.charAt(j - 1) == '*') { dp[i][j] = dp[i][j - 1] || dp[i - 1][j]; } else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1]; } } } return dp[m][n]; }
}
|
可以参照这个思路:https://leetcode-cn.com/problems/wildcard-matching/solution/yi-ge-qi-pan-kan-dong-dong-tai-gui-hua-dpsi-lu-by-/
总结
目前就记录这两题,后续再加