携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情
题目描述
给定一个字符串 (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 = "ab" 输出: true 解释: 第一个 '' 可以匹配空字符串, 第二个 '' 可以匹配字符串 "dce". 示例 5:
输入: s = "acdcb" p = "a*c?b" 输出: false
题目元素
- 给定一个字符串s为目标字符串,p为格式匹配字符串。判断s是否能够被p匹配;
- 字符串s中只包含[a-z]的字母,p中包含[a-z]的字母和'*'、'?';
- 其中字母代表对应的字母;
- '*'代表可以匹配任意字符串,包括空字符串;
- '?'代表匹配一个[a-z]的任意字母。
解题思路
匹配字符串,可以将问题一步一步拆解,会发现它们之间的依赖关系,即后面的子串决定于前面的子串是否匹配,所以可以采用动态规划的方法解决这种类型的题。
动态规划也是源于分治思想,处理的最原始的阶段就是回溯,但是当子问题有重复时,则可以直接用dp table来存储经历过的路径,这时就变成了动态规划。
首先确定动态规划的状态
假定s中的坐标为i,p中的坐标为j。则dp[i][j]代表在s中[0,i]坐标的子符是否和p中[0,j]的的子串匹配;
状态转移方程式
- 当p[j]为字母时,则dp[i][j] = s[i] == p[j] && dp[i-1][j-1];
- 当p[j]为'*'时,则dp[i][j] = dp[i-1][j] || dp[i][j-1];
- 遇到'*'号,因为它可以匹配空串,所以可以跳过当前匹配符号,进行下一个符号的匹配;也可以用当前符号去匹配s中的下一个子串;
- 当p[j]为'?'时,则dp[i][j] = dp[i-1][j-1];
状态临界值
- 当s,p的长度均为0时,dp[0][0]=true;
- 当p长度为0时,dp[i][0]=false;
- 当s长度为0时,当p[j]='*'时,dp[0][j]=true;
代码实现
public static boolean isMatch(String s, String p) {
char[] sChars = s.toCharArray();
char[] pChars = p.toCharArray();
// 默认值为false 初始长度比字符长度大一,因为要存储0位置的值
boolean[][] dp = new boolean[s.length()+1][p.length()+1];
dp[0][0] = true;
// 初始化
for (int j =1;j<= p.length();j++) {
if (pChars[j-1] == '*') {
dp[0][j] = true;
} else {
// 一旦不满足条件,后面的都不满足条件
break;
}
}
for (int i = 1;i <= sChars.length;i ++) {
for (int j = 1;j <= pChars.length;j ++) {
if (pChars[j-1] == '*') {
dp[i][j] = dp[i-1][j] || dp[i][j-1];
} else if (pChars[j-1] == '?') {
dp[i][j] = dp[i-1][j-1];
} else {
// 字母
dp[i][j] = sChars[i-1] == pChars[j-1] && dp[i-1][j-1];
}
}
}
return dp[sChars.length][p.length()];
}