leetcode通配符匹配问题,暴力递归转动态规划

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

image-20221029111135007

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

image-20221029111402370

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;

image-20221029111601753

重点!!!

肝了好久,就卡在这;

初始化完成!!!我们要求的位置是(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:

image-20221029135809673

继续,(j=pStr.length-1,i=str.length-2)位置时,它的dp(j,i+1)为1,所以此位置为1:

image-20221029135953163

同理求出整张表的最后两行:

image-20221029140123603

此时求倒数第三行,(j=pStr.length-3,i=str.length-1),两个位置的字符相等,所以此位置为1;

image-20221029140238039

(j=pStr.length-3,i=str.length-2),两个位置的字符不相等,所以此位置为0;

image-20221029140339819

其他同理,最后的结果为:

image-20221029140448467

最后程序返回(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");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值