通配符匹配 |刷题打卡

363 阅读4分钟

掘金团队号上线,助你 Offer 临门! 点击 查看大厂春招职位

一、题目描述:

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

'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。

两个字符串完全匹配才算匹配成功。

说明:

  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

示例 3:

输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'

示例 4:

输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

示例 5:

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

题目地址:leetcode-cn.com/problems/wi…

二、思路分析:

与第10题 Regular Expression Matching 很类似。

一开始的时候也是使用了第10题的思路,递归。如果当前字符匹配成功,最终结果是之后的字符是否匹配成功。

但是超时了。只能改为动态规划了。

定义一个二维的状态数组 boolean dpArray[s.length()+1][p.length()+1]

其中 dp[i][j] 表示:s的前i个字符与p的前j个字符是否匹配。

我们定义 dpArray[0][0] = true,表示 s = "",p = "" 是匹配成功的。

定义状态转换方程:假设我们已知 dpArray[0...i-1][0...j-1] 的值,那么可以推导出dpArray[i][j]的值

  • p[j] = "*"时有3种情况:
1. p[j] = "*"

    1.1 让"*"匹配 0 个 字符。
        那么 dpArray[i][j] = dpArray[i][j-1]
        
        解释一下:如果 s[i]p[j-1] 匹配,那么 s[i]p[j] 也匹配
        
        s =  abcd p = ab*cd i = 1,j = 2
        dpArray[1][2] = dpArray[1][1]
        
    1.2 让"*" 匹配1个字符
        那么 dpArray[i][j] = dpArray[i-1][j-1]
        
        解释一下:如果 s[i-1]p[j-1] 匹配,那么 s[i]p[j] 也匹配
        s =  abcd p = ab*d i = 2,j = 2
        dpArray[2][2] = dpArray[1][1]
    
    1.3 让"*" 匹配N个字符
        那么 dpArray[i][j] = dpArray[i-1][j]
        
        解释一下:如果 s[i-1]p[j] 匹配,那么 s[i]p[j] 也匹配
        s =  abcd p = a*d i = 2,j = 2
        dpArray[2][1] = dpArray[1][1]
 
  • p[j] = "?"
"?"只能匹配一个字符,所以

dpArray[i][j] = dpArray[i-1][j-1]

解释一下:如果 s[i-1]p[j-1] 匹配,那么 s[i]p[j] 也匹配
  • p[j] = "a-z"

dpArray[i][j] = dpArray[i-1][j-1] && s[i] == p[j]

解释一下:如果 s[i-1]p[j-1] 匹配 并且 s[i] == p[j],那么 s[i]p[j] 也匹配

如果不太理解可以手动实现这个过程:以 s = abcd ,p =  a*d 为例

        ""  a   *   d
    ""  T   F   F   F
 
    a   F   T   T   F
    
    b   F   F   T   F
    
    c   F   F   T   F
    
    d   F   F   T   T

多手写几次这个数组,慢慢就理解了。

最后 dp[s.length()][p.length()]就是最终答案

三、AC 代码:


    private static boolean dfs(String s, String p, int sIndex, int pIndex) {
        if (pIndex == p.length()) {
            //正则以 * 号结尾
            if ('*' == p.charAt(pIndex - 1)) {
                return true;
            }
            //正则结束
            return sIndex == s.length();
        }
        //解决s结束,而正则没有结束 ,s = abcd p = a*
        if (sIndex == s.length()) {
            for (int i = pIndex; i < p.length(); i++) {
                if ('*' != p.charAt(i)) {
                    return false;
                }
            }
            return true;
        }
        if ('*' == p.charAt(pIndex)) {
            boolean flag = false;
            for (int i = sIndex; i < s.length(); i++) {
                flag = dfs(s, p, i, pIndex + 1);
                if (flag) {
                    break;
                }
            }
            return flag;
        }
        if ('?' == p.charAt(pIndex)) {
            return dfs(s, p, sIndex + 1, pIndex + 1);
        }
        if (s.charAt(sIndex) == p.charAt(pIndex)) {
            return dfs(s, p, sIndex + 1, pIndex + 1);
        } else {
            return false;
        }
    }

    public static boolean isMatch(String s, String p) {
        boolean[][] dpArray = new boolean[s.length() + 1][p.length() + 1];
        dpArray[0][0] = true;
        //解决以 * 号开头的问题 例如 s = abc,p = *****c
        for (int i = 0; i < p.length(); i++) {
            if ('*' == p.charAt(i)) {
                dpArray[0][i + 1] = true;
            } else {
                break;
            }
        }
        for (int i = 0; i < s.length(); i++) {
            for (int j = 0; j < p.length(); j++) {
                if ('*' == p.charAt(j)) {
                    dpArray[i + 1][j + 1] = dpArray[i][j] || dpArray[i][j + 1] || dpArray[i + 1][j];
                } else if ('?' == p.charAt(j)) {
                    dpArray[i + 1][j + 1] = dpArray[i][j];
                } else {
                    dpArray[i + 1][j + 1] = (s.charAt(i) == p.charAt(j)) && dpArray[i][j];
                }
            }
        }
        return dpArray[s.length()][p.length()];
    }

四、总结:

我觉得这个是最容易理解的解法了,如果要优化的话,可以使用贪心算法来处理 * 号匹配的问题。甚至更优的AC自动机。但是理解难度高了不少。