持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
通配符匹配
题目会给定一个字符串s和字符串模式p,实现一个支持'?'和'*'的通配符匹配。
- '?'代表可以匹配任意单个字符。
- '*'代表可以匹配任意字符串。
- a-z字符一对一匹配
这个题目是类似正则表达式的简化版。想要解决这题,一开始我的想法是遍历字符串s和字符模式p进行匹配,但会发现由于'*'的存在而变得难以进行下去。但是我又发现用一个方法可以解决‘*’带来的问题。就是动态规划。
我们用动态规划解决的问题是:在某个下标前的字符串,他是否能与字符模式匹配。
为了能让大家更好的理解,我直接引入一个示例来讲解。
输入:
s = "acdcb"
p = "a*c??b"
输出: true
我们先建立动态规划所需的二维数组dps。
| 空字符 | a | * | c | ? | ? | b | |
|---|---|---|---|---|---|---|---|
| 空字符 | |||||||
| a | |||||||
| c | |||||||
| d | |||||||
| c | |||||||
| b |
相信大家看到表中所写的空字符串肯定会有所疑惑,这实际上是考虑到了几种特殊情况。
- 字符串
s和字符模式p都为空字符串时,返回结果应该为true - 字符串
s为空字符串,字符模式p为'*'或为连续的‘*’时,返回结果应该为true - 字符串
s不是空字符串,字符模式p为空时,返回结果应该为false
从二维数组上看其实就是初始化了最上面一行和最左边一行。用代码表示为:
boolean[][] dps = new boolean[m][n];
dps[0][0] = true;
for (int i = 1; i < n; i++) {
if (p.charAt(i - 1) == '*') {
dps[0][i] = true;
}else{
//如果 * 不连续,则为false
break;
}
}
for(int i = 1; i < n;i++){
dps[i][0] = false;
}
由于dps创建时内部数据默认值为false,所以可以简化成:
boolean[][] dps = new boolean[m][n];
dps[0][0] = true;
for (int i = 1; i < n; i++) {
if (p.charAt(i - 1) == '*') {
dps[0][i] = true;
}else{
break;
}
}
对示例进行初始化可得出下表
| 空字符 | a | * | c | ? | ? | b | |
|---|---|---|---|---|---|---|---|
| 空字符 | T | F | F | F | F | F | F |
| a | F | ||||||
| c | F | ||||||
| d | F | ||||||
| c | F | ||||||
| b | F |
里面空白部分默认是false,但是为了观感,这里就把空白部分置为空。
接下来将动态规划的核心操作,即我们的具体匹配规则。
当字符模式p为 a-z 时,那字符串s也必须为一个相同的 a-z 。同理当字符模式p为 ? 时,那字符串s可以为任意 a-z 的字符 。应为a- z字符匹配和'?'匹配都是对于单个字符的,所以我们可以把判断放在一起。
p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)
如果匹配成功,那根据动态规划的原理,我们要和上一步的结果联系起来,如果上一个字符匹配成功,那么到该字符位置为止也是匹配成功的。
if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)){
dps[i][j] = dps[i - 1][j - 1];
}
字符模式p为 * 时,那字符串s可以为任意多个 a-z 的字符 。
p.charAt(j - 1) == '*'
如果为true,根据动态规划的原理我们也要联系到上一步的结果,但是因为'*'为多字符串匹配,他有两种情况:
一种是不使用‘*’,把他看成空字符串。
dps[i][j] = dps[i][j-1]
另一种当然是使用‘*’,把他和字符串进行匹配。
dps[i][j] = dps[i][j-1]
这一步有点难懂,给大家举个例子
如果看成 a 与 a* 做匹配的话,可以发现*与空字符做了匹配。
如果看成 abb 与 a* 做匹配的话,可以发现*在图表上是与bb做了匹配。
如果看成 abb 与 a*b做匹配的话,可以发现*b在图表上是与bb做了匹配。
将几个核心操作按照逻辑结合,可以得到代码:
if(p.charAt(j - 1) == '*'){
dps[i][j] = dps[i-1][j] || dps[i][j-1];
}else if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)){
dps[i][j] = dps[i - 1][j - 1];
}
最终我们返回二维数组末尾的结果即可。 \
| 空字符 | a | * | c | ? | ? | b | |
|---|---|---|---|---|---|---|---|
| 空字符 | T | F | F | F | F | F | F |
| a | F | T | T | F | F | F | F |
| c | F | F | T | T | F | F | F |
| d | F | F | T | F | T | F | F |
| c | F | F | T | T | F | T | F |
| b | F | F | T | F | T | F | T |
代码
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length() + 1;
int n = p.length() + 1;
boolean[][] dps = new boolean[m][n];
dps[0][0] = true;
for (int i = 1; i < n; i++) {
if (p.charAt(i - 1) == '*') {
dps[0][i] = true;
}else{
break;
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if(p.charAt(j - 1) == '*'){
dps[i][j] = dps[i-1][j] || dps[i][j-1];
}else if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)){
dps[i][j] = dps[i - 1][j - 1];
}
}
}
return dps[m - 1][n - 1];
}
}