HJ71 字符串通配符-- 包含动态规划、递归、字符串通配符三种方法

带有通配符的字符串匹配
一、Leetcode | 44 Wildcard Matching(只有一个字符串包含通配符)


题目很简单,就是说两个字符串,一个含有通配符,去匹配另一个字符串;输出两个字符串是否一致。

注意:’?’表示匹配任意一个字符,’*’表示匹配任意字符0或者多次

首先,我们想到暴力破解。如果从头到尾的破解,到第二个字符时,是否匹配成功取决于第一个字符是否匹配成功! 所以我们想到应该要用到动态规划;

既然用到动态规划,最重要的是设置初值 和找到递推式:

于是,我们开始分析初值怎么设;其实很简单,把这个匹配问题可以想象成一个矩阵dp,纵轴代表含有通配符的匹配字符串s2, 横轴代表要匹配的字符串s1。假设现在s2=”a*b”, s1=”abc” 如图:

对应空位就是截止到当前的 (i,j) 位置,两字符串是否匹配。匹配为 T(true),不匹配为 F(false),最后返回最右下角的值,就是当前两个字符串是否匹配的最终值;

现在我们要做的设置初值,所以我们大可多加一行和一列,来填充初值;s1既然是要匹配的,我们都设为 F(即dp[0][1]=F,dp[0][2]=F,dp[0][3]=F),表示当前还未开始匹配。而s2的初值,我们发现如果星号和a调换位置,星号可以匹配任意字符串,所以dp[i][0]的值取决于该位置是否为星号和上一个位置d[i-1][0]是否为T(其实就是上一个位置是否也是星号),所以我们设置dp[0][0]为 T。所以形成下图:

这里写图片描述

此时初值已经设置完毕,我们要找到递推式;经局部推算,我们发现递推式应该有两种,一种是当s2的字符是星号,另一种是s2的字符是非星号

先看星号的情况:当要计算dp[2][1](即要匹配a*和a时),我们发现是取决于dp[1][1](即a和a是否匹配),当要计算dp[2][2] (即要匹配a*和ab时),是取决于dp[2][1] (即a*和a是否匹配)。抽象一下,星号和任意字符(0或多个)都匹配。所以字符串截止到星号匹配的情况,取决于当前位置向上和向左的情况(即可以为0个字符,也可以为多个字符)。所以此时递推式为                   dp[i][j]=dp[i−1][j]||dp[i][j−1] 如图:

这里写图片描述

再看非星号的情况:当要计算dp[3][2] (即要匹配a*b和ab时),则取决于dp[2][1]和a[3][2] (即a*和a是否匹配,同时b和b是否匹配);所以可以得到递推式 dp[i][j] = dp[i-1][j-1]&&a[i][j]。如图:

这里写图片描述

最后我们得到了初值和两个递推式,就可以上代码了;
 

//isMatch: s1无通配符,s2有通配符, '?'表示匹配任意一个字符,'*'表示匹配任意字符0或者多次
    public static boolean isMatch(String s1, String s2) {
        int countXing = 0;
        for(char c : s2.toCharArray())
            countXing++;
        if(s2.length() - countXing > s1.length() ) //说明s2去掉通配符,长度也长于s1
            return false;

        //动态规划设置初值
        boolean[][] dp = new boolean[s2.length()+1][s1.length()+1]; 
        dp[0][0] = true;

        for(int i=1; i<=s2.length(); i++) {
            char s2_char = s2.charAt(i-1);
            dp[i][0] = dp[i-1][0] && s2_char=='*'; //设置每次循环的初值,即当星号不出现在首位时,匹配字符串的初值都为false
            for(int j=1; j<=s1.length(); j++) {
                char s1_char = s1.charAt(j-1);
                if(s2_char == '*') 
                    dp[i][j] = dp[i-1][j] || dp[i][j-1]; //动态规划递推式(星号) 表示星号可以匹配0个(决定于上次外循环的结果)或者多个(决定于刚才内循环的结果)
                else 
                    dp[i][j] = dp[i-1][j-1] && (s2_char=='?' || s1_char == s2_char); //动态规划递推式(非星号) 表示dp值取决于上次的状态和当前状态
            }
        }
        return dp[s2.length()][s1.length()];
    }

 //方法二:字符串通配符(牛客超时,内有解决方法)

//方法二:字符串通配符
    public static  boolean getOc(String in,String temp){
        in = in.toUpperCase(); 
        temp = temp.toUpperCase(); 
        in = in.replaceAll("(\\*)\\1{1,}","*");//将连续的“*”,替换成一个“*”,牛客超时
        
        //字符串通配符
        in = in.replaceAll("\\?", "[0-9A-Za-z]{1}");
        in = in.replaceAll("\\*", "[0-9A-Za-z]{0,}");
        //in = in.replaceAll("\\.", "\\\\.");
        return temp.matches(in);
    }

//方法一:递归(牛客超时).s1匹配,p1为s1索引,s2为目标串,p2为s2索引

//方法一:递归.s1匹配,p1为s1索引,s2为目标串,p2为s2索引
    public static boolean judge(String s1, String s2, int p1, int p2) {
        //把s1的连续多个“*”变为1一个‘*’,
        s1 = s1.replaceAll("(\\*)\\1{1,}","*");//replace替换char和string。replaceAll将正则表达式替换成String
        
        s1 = s1.toUpperCase(); 
        s2 = s2.toUpperCase(); 
        
        if(p1 == s1.length() && p2 == s2.length()) {
            return true;
        }
        
        if(p1 == s1.length() || p2 == s2.length()) {
            return false;
        }
        
        if(s1.charAt(p1) == '*') {
            //匹配多个judge(s1,s2,p1,p2+1),匹配0个judge(s1,s2,p1+1,p2);匹配1个judge(s1,s2,p1+1,p2+1);
            return judge(s1,s2,p1,p2+1) || judge(s1,s2,p1+1,p2) || judge(s1,s2,p1+1,p2+1);
        }else if(s1.charAt(p1) == '?' || s1.charAt(p1) == s2.charAt(p2)) {
            return judge(s1,s2,p1+1,p2+1);
        }else {
            return false;
        }
    }


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值