动态规划 | 带有通配符的字符串匹配(浅显易懂)

来源:互联网 发布:塑料水晶高跟鞋淘宝 编辑:程序博客网 时间:2024/03/29 05:20

自己的C++实现包含?和*通配符的程序,肯定有bug,欢迎指出:)

ostream& operator<<(ostream& o, const vector<vector<int> >& v){    for(int i=0; i<v.size(); i++){        vector<int> vv = v[i];        for(int j=0; j<vv.size(); j++){            o<<vv[j]<<" ";        }        o<<endl;    }    return o;}int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);    string str1, str2;    cin>>str1>>str2;    vector<vector<int> > array(str1.size()+1, vector<int>(str2.size()+1, 0));    for(int i=0; i<str1.size(); i++){        for(int j=0; j<str2.size(); j++){            //初始化            //判断第一个字符是不是匹配,若匹配则置1,否则置0            if(i==0&&j==0){                if(str1[i]=='*' || str2[j]=='*' || str1[i]=='?' || str2[j]=='?' || str1[i]==str2[j]){                    array[i+1][j+1]=1;                    continue;                }                else{                    cout<<"Not Match!";                    return 0;                }            }            //若出现*通配符,则检查左,上和左上三个方向的状态            if(str1[i]=='*' || str2[j]=='*'){                if(array[i][j+1]==1 || array[i+1][j]==1 || array[i][j]==1){                    array[i+1][j+1]=1;                }            }            //若出现?通配符,则检查左上的状态            else if(str1[i]=='?' || str2[j]=='?'){                if(array[i][j]==1 || array[i][j]==2){                    array[i+1][j+1]=2;                }            }            else if(str1[i]==str2[j]){                if(array[i][j+1]==1 || array[i+1][j]==1 || array[i][j]==1 || array[i][j]==2){                    array[i+1][j+1]=1;                }            }        }    }    int result = array[str1.size()][str2.size()];    if(result==1 || result==2) cout<<"Match!";    else cout<<"Not Match!";    //cout<<array;    return 0;}


以下转载自 http://blog.csdn.net/gldemo/article/details/47678159


带有通配符的字符串匹配

一、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[i1][j]||dp[i][j1] 如图:

这里写图片描述

再看非星号的情况:当要计算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()];    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

PS: 两个字符串都包含通配符的解法

通过上面那个列子,其实就这个问题就很容易想了。

首先就是初值的设置,两个字符串都按上题中的包含通配符的字符串设置初值的方法,根据是否为星号和上一个的状态。

其次就是递推式,它不用变,只是需要同时判断两个字符串中是否都包含通配符。

直接上代码:

public static boolean isMatchByBoth(String s1, String s2) {            //动态规划设置初值            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);                    dp[0][j] = dp[0][j-1] && s1.charAt(j-1)=='*';                    if(s2_char == '*' || s1_char == '*') {                        dp[i][j] = dp[i-1][j] || dp[i][j-1]; //动态规划递推式(星号) 表示星号可以匹配0个(决定于上次外循环的结果)或者多个(决定于刚才内循环的结果)                    } else {                        dp[i][j] = dp[i-1][j-1] && (s1_char=='?' || s2_char=='?' || s1_char == s2_char);                    }                }            }            return dp[s2.length()][s1.length()];        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21


二、Leetcode | 10 Regular Expression Matching(正则通配符)


这道题是把*的概念变了,它代表匹配星号之前元素的0个或多个。即 c* 带便0个或者多个c。

所以具体代码和思路写到代码注释里了。大家可以对照上面的题看看。 

public class Solution {    public boolean isMatch(String s, String p) {       //有两个假设,一个是不会出现c**的格式;第二个打头的一定是字母       //dp[i, j] means matching status between s.Substring(0, j) and p.Substring(0, i)       boolean[][] dp = new boolean[p.length()+1][s.length()+1];       dp[0][0] = true;       for(int i=1; i<=p.length(); i++) {           char pchar = p.charAt(i-1);           //dp[i, 0] means if patter.Substring(0, i) matches empty string           if(i > 1 && pchar=='*') dp[i][0] = dp[i-2][0];            for(int j=1; j<=s.length(); j++) {                char schar = s.charAt(j-1);               if(i > 1 && pchar == '*') {                   //p可以匹配多个或0个pchar元素,所以检查上一个,是是否匹配多个,检查上上一个,是匹配了0个,检查上上一个匹配元素的状态(这是竖着的)                   dp[i][j] = dp[i-2][j] || dp[i-1][j];                   if(j > 1 && match(schar, p.charAt(i-2))) //从第二列(p的第二个字符开始),是否有连续匹配                        dp[i][j] = dp[i][j] || dp[i][j-1];               } else {                   dp[i][j] = match(schar, pchar) && dp[i-1][j-1];               }           }       }       return dp[p.length()][s.length()];    }    boolean match(char c, char p) {        if (p == '.') return true;        else return c == p;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
阅读全文
0 0