leetcode通配符匹配问题,暴力递归转动态规划
第一次独立不看题解完成了困难题,当然要记录我解题的过程啦,dp时间复杂度击败java提交91.77%用户。
暴力递归:
分为了isMatch模块,和process递归模块
isMatch模块
主要是进行预处理,题目说明了字符串s和匹配模式p有空串的情况;
public static boolean isMatch(String s, String p) {
// s为字符串,p为匹配模式,s和p都为空,匹配成功返回
if (s.equals("") && p.equals("")) {
return true;
} else if (s.equals("")) {
// s为空,但p不为空,例如p为"****",这种情况是可以匹配成功的,因为题目说明了*可以匹配空串
char[] pStr = p.toCharArray();
for (char c : pStr) {
// 如果p不全是*,返回匹配失败
if (c != '*') return false;
}
// 如果没有在循环内返回,说明p全是*,返回匹配成功
return true;
} else if (p.equals("")) {
// s不为空,但是p为空,直接返回匹配失败
return false;
} else {
// s不为空,p也不空的情况
char[] str = s.toCharArray();
char[] pStr = p.toCharArray();
// 暴力递归,i表示此时s遍历的位置,j表示此时p匹配的位置
return process(str, pStr, 0, 0);
}
}
process递归模块
private static boolean process(char[] str, char[] pStr, int i, int j) {
// 分下面四种情况
// 1、如果i、j分别等于各自串的长度,说明匹配成功,这里注意,如果中途有字符不匹配的情况,是到不了这个位置的,(下面的字符未匹配成功,直接返回false不会再往下遍历)
if (i == str.length && j == pStr.length) {
return true;
} else if (i == str.length) {
// 2、i遍历完s,但是j还没遍历完p,现在看从此刻的j及j往后的位置是否全部是*
int k=j;
for(;k<pStr.length;k++){
// 如果某个j位置不是*,返回匹配失败
if(pStr[k]!='*')
return false;
}
// 程序到这个位置,没在上面的循环中返回,说明此刻的j及j往后的位置全部是*,返回匹配成功
return true;
}else if(j == pStr.length)
// 3、 j遍历完了p,但是i还没遍历完s,直接返回匹配失败
return false;
// 4、 i、j都未遍历完各自的字符串
if (str[i] == pStr[j] || pStr[j] == '?') {
// (1)i与j位置的字符匹配成功,继续往下遍历i+1、j+1的位置
return process(str, pStr, i + 1, j + 1);
} else if (pStr[j] == '*') {
// (2)i与j位置的字符未匹配成功,但是j位置是*,这个时候就需要枚举*匹配多少字符的所有情况
// k从0开始遍历,k表示*匹配k个字符
for (int k = 0; k + i <= str.length; k++) {
// 在枚举的过程中,如果出现了true,表示本层递归i、j的返回结果为true
if (process(str, pStr, i + k, j + 1)) {
return true;
}
}
// 在枚举的过程中,没有出现了true,则本层递归i、j的返回结果为false
return false;
} else {
// (3)i、j位置的字符不相等,且j位置不为*,说明这种匹配方法不可取,不需要往下递归了
return false;
}
}
动态规划:
暴力递归的方法肯定是会时间超限的,简单分析以下为什么会时间超限:
s="abcdeff" p="******"
i=3,j=5时需要算几遍?前5个*吃掉“abc”的所有组合方法就是答案,就这一个位置,就要算这么多遍,那还有i=4,j=5的情况、i=5,j=5…肯定超时;
这个时候就想到了记忆化搜索,将第一次到达这个位置的结果记录下来,带着这个结果玩递归,到达某个位置时先查记录,如果查到了就不用玩递归了,直接返回;大大降低了复杂度;
但是动态规划比记忆化搜索还高效,直接填表即可;上面的暴力递归的可变参数只有i、j,说明需要一个二维的矩阵,大小为对应字符串的长度(n、m)再分别+1;时间复杂度为O((n+1)*(m+1))
基本思路
利用上面的暴力递归process中的四种情况的前三种初始化dp矩阵,再利用第四种情况,加上dp中已经填好的位置,处理dp中的其它位置;
用下面的例子结合代码详细讲解:
s="adceb" p="*a*b**"
初始化:
char[] str = s.toCharArray();
char[] pStr = p.toCharArray();
int[][] dp=new int[pStr.length+1][str.length+1];
dp[pStr.length][str.length]=1;
// 对应暴力递归process中的:
// 1、如果i、j分别等于各自串的长度,说明匹配成功,这里注意,如果中途有字符不匹配的情况,是到不了这个位置的,(下面的字符未匹配成功,直接返回false不会再往下遍历)
// if (i == str.length && j == pStr.length) {
// return true;
// }
char t='*';
for(int k=pStr.length-1;k>=0;k--){
if(pStr[k]=='*')
dp[k][str.length]=1;
else {
break;
}
}
// 对应暴力递归process中的:
// else if (i == str.length) {
2、i遍历完s,但是j还没遍历完p,现在看从此刻的j及j往后的位置是否全部是*
// int k=j;
// for(;k<pStr.length;k++){
如果某个j位置不是*,返回匹配失败
// if(pStr[k]!='*')
// return false;
// }
程序到这个位置,没在上面的循环中返回,说明此刻的j及j往后的位置全部是*,返回匹配成功
// return true;
// }
for(int k=0;k< str.length;k++){
dp[pStr.length][k]=0;
}
// 对应暴力递归process中的:
//else if(j == pStr.length)
3、 j遍历完了p,但是i还没遍历完s,直接返回匹配失败
// return false;
重点!!!
肝了好久,就卡在这;
初始化完成!!!我们要求的位置是(0,0)位置,分析一下:j位置有两种情况普通字符(这里将‘?’看成普通字符)和*;
1)如果j位置是普通字符,求dp(j,i)位置的值,首先看本身i、j位置的值是否相等,如果不相等,则直接可以求得dp(j,i)为0;如果相等,则还需看dp(j+1,i+1)的值是否等于1,如果等于1,dp(j,i)才为1,否则dp(j,i)为0;
2)如果j位置是*,那么除了与1)一样要看dp(j+1,i+1)外,还需要看dp(j+1,i)位置的值和dp(j,i+1)的值;三种情况分别解释:
dp(j+1,i+1):说明j位置的*,只匹配到i位置就结束了,往后继续去处理j+1,i+1的位置;
dp(j+1,i):说明j位置的*,只匹配到i-1位置就结束了(没有选择继续吃掉i位置的字符),往后继续去处理j+1,i的位置;
dp(j,i+1):说明j位置的*,吃掉了i位置的字符,并且继续尝试去吃i+1位置的字符;
总结以下这种情况,dp(j+1,i+1)、dp(j+1,i)、dp(j,i+1)三个位置只要有一个是1,dp(j,i)就为1,否则为0;
清楚它的思想后,毫无疑问是从最右下的位置开始填表,顺序为从右往左,从下往上:
for(int j=pStr.length-1;j>=0;j--){
for(int i=str.length-1;i>=0;i--){
if(pStr[j]=='*'){
if(dp[j][i+1]==1||dp[j+1][i]==1||dp[j+1][i+1]==1)
dp[j][i]=1;
}else {
if(pStr[j]==str[i]||pStr[j]=='?'){
if(dp[j+1][i+1]==1)
dp[j][i]=1;
else dp[j][i]=0;
}else {
dp[j][i]=0;
}
}
}
}
(j=pStr.length-1,i=str.length-1)位置时,它的dp(j+1,i+1)、dp(j,i+1)都为1,所以此位置为1:
继续,(j=pStr.length-1,i=str.length-2)位置时,它的dp(j,i+1)为1,所以此位置为1:
同理求出整张表的最后两行:
此时求倒数第三行,(j=pStr.length-3,i=str.length-1),两个位置的字符相等,所以此位置为1;
(j=pStr.length-3,i=str.length-2),两个位置的字符不相等,所以此位置为0;
其他同理,最后的结果为:
最后程序返回(0,0)位置的值即可;
完整代码:
package test2;
public class IsMatch {
public static boolean dpWay(String s, String p) {
if (s.equals("") && p.equals("")) {
return true;
} else if (s.equals("")) {
char[] pStr = p.toCharArray();
for (char c : pStr) {
if (c != '*') return false;
}
return true;
} else if (p.equals("")) {
return false;
} else {
char[] str = s.toCharArray();
char[] pStr = p.toCharArray();
int[][] dp=new int[pStr.length+1][str.length+1];
dp[pStr.length][str.length]=1;
// 对应暴力递归process中的:
// 1、如果i、j分别等于各自串的长度,说明匹配成功,这里注意,如果中途有字符不匹配的情况,是到不了这个位置的,(下面的字符未匹配成功,直接返回false不会再往下遍历)
// if (i == str.length && j == pStr.length) {
// return true;
// }
char t='*';
for(int k=pStr.length-1;k>=0;k--){
if(pStr[k]=='*')
dp[k][str.length]=1;
else {
break;
}
}
// 对应暴力递归process中的:
// else if (i == str.length) {
2、i遍历完s,但是j还没遍历完p,现在看从此刻的j及j往后的位置是否全部是*
// int k=j;
// for(;k<pStr.length;k++){
如果某个j位置不是*,返回匹配失败
// if(pStr[k]!='*')
// return false;
// }
程序到这个位置,没在上面的循环中返回,说明此刻的j及j往后的位置全部是*,返回匹配成功
// return true;
// }
for(int k=0;k< str.length;k++){
dp[pStr.length][k]=0;
}
// 对应暴力递归process中的:
//else if(j == pStr.length)
3、 j遍历完了p,但是i还没遍历完s,直接返回匹配失败
// return false;
for(int j=pStr.length-1;j>=0;j--){
for(int i=str.length-1;i>=0;i--){
if(pStr[j]=='*'){
if(dp[j][i+1]==1||dp[j+1][i]==1||dp[j+1][i+1]==1)
dp[j][i]=1;
}else {
if(pStr[j]==str[i]||pStr[j]=='?'){
if(dp[j+1][i+1]==1)
dp[j][i]=1;
else dp[j][i]=0;
}else {
dp[j][i]=0;
}
}
}
}
return dp[0][0] == 1;
}
}
public static boolean isMatch(String s, String p) {
// s为字符串,p为匹配模式,s和p都为空,匹配成功返回
if (s.equals("") && p.equals("")) {
return true;
} else if (s.equals("")) {
// s为空,但p不为空,例如p为"****",这种情况是可以匹配成功的,因为题目说明了*可以匹配空串
char[] pStr = p.toCharArray();
for (char c : pStr) {
// 如果p不全是*,返回匹配失败
if (c != '*') return false;
}
// 如果没有在循环内返回,说明p全是*,返回匹配成功
return true;
} else if (p.equals("")) {
// s不为空,但是p为空,直接返回匹配失败
return false;
} else {
// s不为空,p也不空的情况
char[] str = s.toCharArray();
char[] pStr = p.toCharArray();
// 暴力递归,i表示此时s遍历的位置,j表示此时p匹配的位置
return process(str, pStr, 0, 0);
}
}
private static boolean process(char[] str, char[] pStr, int i, int j) {
// 分下面四种情况
// 1、如果i、j分别等于各自串的长度,说明匹配成功,这里注意,如果中途有字符不匹配的情况,是到不了这个位置的,(下面的字符未匹配成功,直接返回false不会再往下遍历)
if (i == str.length && j == pStr.length) {
return true;
} else if (i == str.length) {
// 2、i遍历完s,但是j还没遍历完p,现在看从此刻的j及j往后的位置是否全部是*
int k=j;
for(;k<pStr.length;k++){
// 如果某个j位置不是*,返回匹配失败
if(pStr[k]!='*')
return false;
}
// 程序到这个位置,没在上面的循环中返回,说明此刻的j及j往后的位置全部是*,返回匹配成功
return true;
}else if(j == pStr.length)
// 3、 j遍历完了p,但是i还没遍历完s,直接返回匹配失败
return false;
// 4、 i、j都未遍历完各自的字符串
if (str[i] == pStr[j] || pStr[j] == '?') {
// (1)i与j位置的字符匹配成功,继续往下遍历i+1、j+1的位置
return process(str, pStr, i + 1, j + 1);
} else if (pStr[j] == '*') {
// (2)i与j位置的字符未匹配成功,但是j位置是*,这个时候就需要枚举*匹配多少字符的所有情况
// k从0开始遍历,k表示*匹配k个字符
for (int k = 0; k + i <= str.length; k++) {
// 在枚举的过程中,如果出现了true,表示本层递归i、j的返回结果为true
if (process(str, pStr, i + k, j + 1)) {
return true;
}
}
// 在枚举的过程中,没有出现了true,则本层递归i、j的返回结果为false
return false;
} else {
// (3)i、j位置的字符不相等,且j位置不为*,说明这种匹配方法不可取,不需要往下递归了
return false;
}
}
public static void main(String[] args) {
// String s = "abcabczzzde";
// String p = "*abc???de*";
String s = "adceb";
String p = "*a*b**";
System.out.println(isMatch(s, p));
System.out.println(dpWay(s,p)+" dp");
}
}
lse;
} else {
// (3)i、j位置的字符不相等,且j位置不为*,说明这种匹配方法不可取,不需要往下递归了
return false;
}
}
public static void main(String[] args) {
// String s = "abcabczzzde";
// String p = "*abc???de*";
String s = "adceb";
String p = "*a*b**";
System.out.println(isMatch(s, p));
System.out.println(dpWay(s,p)+" dp");
}
}