算法题目 -- 通过符匹配

68 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

通配符匹配

题目会给定一个字符串s和字符串模式p,实现一个支持'?'和'*'的通配符匹配。

  • '?'代表可以匹配任意个字符。
  • '*'代表可以匹配任意字符串。
  • a-z字符一对一匹配

这个题目是类似正则表达式的简化版。想要解决这题,一开始我的想法是遍历字符串s和字符模式p进行匹配,但会发现由于'*'的存在而变得难以进行下去。但是我又发现用一个方法可以解决‘*’带来的问题。就是动态规划。

我们用动态规划解决的问题是:在某个下标前的字符串,他是否能与字符模式匹配。

为了能让大家更好的理解,我直接引入一个示例来讲解。

输入:
s = "acdcb"
p = "a*c??b"
输出: true

我们先建立动态规划所需的二维数组dps

空字符a*c??b
空字符
a
c
d
c
b

相信大家看到表中所写的空字符串肯定会有所疑惑,这实际上是考虑到了几种特殊情况。

  1. 字符串s和字符模式p都为空字符串时,返回结果应该为true
  2. 字符串s为空字符串,字符模式p为'*'或为连续的‘*’时,返回结果应该为true
  3. 字符串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
空字符TFFFFFF
aF
cF
dF
cF
bF

里面空白部分默认是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]

这一步有点难懂,给大家举个例子

图片.png

如果看成 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
空字符TFFFFFF
aFTTFFFF
cFFTTFFF
dFFTFTFF
cFFTTFTF
bFFTFTFT

代码

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];
    }
}