题解 | #字符串通配符#动态规划

字符串通配符

https://www.nowcoder.com/practice/43072d50a6eb44d2a6c816a283b02036

一、正则匹配

我一开始想到的是使用正则匹配的方式去做。

* 按题意用正则表示则是[0-9a-z]*。

?按题意用正则表示则是[0-9a-z]。

另外特殊字符.,需要前面加上\\进行转移,这样把匹配串转化成了一个正则匹配串,就可以之间用String的方法直接获取结果。

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNextLine()) {
            String pattern = in.nextLine().toLowerCase();
            String s = in.nextLine().toLowerCase();

            char[] chars = pattern.toCharArray();
		  //sb就是正则匹配字符串
            StringBuilder sb = new StringBuilder();
		  //这个用来合并多个连续的*
             char lastc = ' ';
            for (int i = 0; i < chars.length;i++) {
                char c = chars[i];
                if (i != 0){
                    lastc = chars[i-1];
                }
                if (c == '*' ){
                    if (lastc != '*'){
                        sb.append("[0-9a-z]*");
                    }
                    continue;
                }
                if (c == '?'){
                    
                        sb.append("[0-9a-z]");
                
                   
                    continue;
                }
                if (c == '.'){
                    sb.append("\\.");
                    continue;
                }
                sb.append(c);
            }

            System.out.println(s.matches(sb.toString()));

        }
    }
}

二、动态规划

这个可以看看我之前两串最大子串的那个题解https://www.nowcoder.com/feed/main/detail/f952886975314dea8eafa66de153caa5

这题类似,首先定义dp[i][j],表示待匹配的字符串S的前i个字符组成的子串,和匹配串P的前j个字符组成的子串,之间的匹配状态。用true表示匹配成功。

由简至繁,i的数值是从1到S的长度值,j的数值范围是1到P的长度值,我们需要找出从1->length变化的规律。因为是判断S满不满足P的形式,我们先确定S,再动P。那么对每个场景的遍历方式为:

for (int i = 1; i <= s.length ; i++) {
  for (int j = 1; j <= p.length; j++) {
	//这里就是处理:S的前i个字符,和P的前j个字符的匹配结果
  }
}

循环体中的每个场景下,p的第j个字符p[j-1] (注意索引是0开始,所以要-1)都会遇到什么情况呢?就三种。

1、为通配符*

这个情况下,一是不匹配任何字符,直接把当前p的第j个字符忽略掉,那么dp[i][j]的值就取决于dp[i][j-1];

二是匹配字符,那么就需要把匹配的S的第i个字符忽略掉,dp[i][j]的值就取决于dp[i-1][j],但这需要有一个前提,就是S的第i个字符是字母或数字。

所以:dp[i][j] = dp[i][j-1] || ( s[i-1] = 字母或数字 && dp[i-1][j] )

2、为通配符?

当P第j个字符是?,那S想要匹配就必须是字母或数字,然后匹配上了,还要求S的前i-1个字符与P的前j-1个字符需要匹配。

所以: dp[i][j] = s[i-1] = 字母或数字 && dp[i-1][j]

3、为非通配符

这个是最好处理的,直接比较,但是需要注意忽略大小写,这个需要注意,我是先转成了小写统一比较

所以:dp[i][j] = (s[i-1] == p[j-1]) && dp[i-1][j-1]

for (int i = 1; i <= s.length ; i++) {
	for (int j = 1; j <= p.length; j++) {
		if (p[j - 1] == '*'  ) {
			//当匹配字符为*时,那么要不匹配:dp[i][j] = dp[i][j - 1],
			//或者匹配字符,但是前字符必须是字母或数字才可以
			dp[i][j] = dp[i][j - 1] || 
			  ( (Character.isLetter(s[i - 1]) || Character.isDigit(s[i - 1])) && dp[i - 1][j]) ;
		} else if (p[j - 1] == '?' ) {
			//如果是?,那么匹配的字符首先要是字母或数字,然后出去匹配的字符,前面的字符要能匹配
			dp[i][j] =  (Character.isLetter(s[i - 1]) || 
						 Character.isDigit(s[i - 1])) && dp[i - 1][j - 1] ;
		} else {
			//如果是其它字符,那忽略大小写,需要相等才能匹配成功
			dp[i][j] = Character.toLowerCase(s[i - 1]) == Character.toLowerCase(p[j - 1]) 
			  && dp[i - 1][j - 1];
		}
	}
}

这里有一个可能会有问题的就是:当p的第j个字符为*时,它转移方程是:

dp[i][j] = dp[i][j-1] || ( s[i-1] = 字母或数字 && dp[i-1][j] )

假设没有字母或数字的要求,*可以匹配任意一个字符,那么可以匹配0个,1个,2个。。。。。一直到匹配k个字符,可以为1到S长度直接的任意值。它的转移方程应当是:

dp[i][j] = dp[i][j-1] || dp[i-1][j] || dp[i-2][j] ....................dp[i-k][j]

这样子好像更符和我们思考的方式,匹配多个,但是用的动态规划,其实后面那个dp[i-k][j]是处理过的。就上面那个公式后面一部分【dp[i-1][j] || dp[i-2][j] ....................dp[i-k][j] 】可以直接表示为dp[i-1][j],因为我们在算dp[i-1][j]的时候,按上面的公式会算上【dp[i-1][j] || dp[i-2][j] ....................dp[i-k][j] 】。

全部评论

相关推荐

下北泽:都是校友,还是同届,我就说直白点,不委婉了,我相信你应该也不是个玻璃心,首先你觉得一个双非的绩点写简历上有用吗?班长职务有用吗?ccf有用吗?企业会关心你高数满分与否吗?第二,第一个项目实在太烂,一眼就能看出是外卖,还是毫无包装的外卖,使用JWT来鉴权,把热点数据放进Redis这两个点居然还能写进简历里,说难听点这两个东西都是学个几十分钟,调用个API就能完成的事情,在双非一本的条件下,这种项目你觉得能拿出手吗,第二个项目你写的东西和你的求职方向有任何的匹配吗?第三,计设那一块毫无价值,如果想突出自己会前端,直接写入专业技能不行吗,最后,专业技能里像深入理解JVM底层原理这种你觉得这句话你自己真的能匹配吗?都是校友加上同届,我措辞直接,但希望能点出你的问题,想进大厂还得继续沉淀项目和学习
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务