LeetCode 44. Wildcard Matching

一 题目

Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for '?' and '*'.

'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

Note:

  • s could be empty and contains only lowercase letters a-z.
  • p could be empty and contains only lowercase letters a-z, and characters like ? or *.

Example 1:

Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:

Input:
s = "aa"
p = "*"
Output: true
Explanation: '*' matches any sequence.

Example 3:

Input:
s = "cb"
p = "?a"
Output: false
Explanation: '?' matches 'c', but the second letter is 'a', which does not match 'b'.

Example 4:

Input:
s = "adceb"
p = "*a*b"
Output: true
Explanation: The first '*' matches the empty sequence, while the second '*' matches the substring "dce".

Example 5:

Input:
s = "acdcb"
p = "a*c?b"
Output: false

Accepted 195,544 Submissions  836,511

二 分析

      求通配符匹配问题,hard级别,跟之前做过的  10. Regular Expression Matching 很类似。主要区别在于星号的作用(10的*前面必须有字母)。它也有两个特殊的字符"?"和"*"。其其中"?"表示匹配一个当前的任意字符,不看星号的话,还是一一对应的,但是对于星号的处理,可以出现在任意位置,可以匹配任意字符串,也是本题的难点所在。

      当然* 也有限制,就是受到位置限制: p中有s中不存在的字符,那么一定无法匹配。或者对于p星号之后字母,*不能决定。

因此,要分析不同的情况:

   1) * 能全匹配:如s=abbcd,p=a*;

    2)  部分匹配:s=abbcd,p=a*cd

规律如下:

1.假设碰到*号时s串的位置是i, p串的位置是j,则将i+1作为*号后续进行比较的起点。

2.从j开始与i+1位置的元素进行比较,如果相同,再按照常规匹配的方式往后面继续比。否则l跳到前面和i+1开始比较那个位置的后面一个。同时p这边的元素也要归位到j+1

2.1  动态规划法

  动态规划法适合处理这类字符串的问题,把字符串与子串的关系用动态规划的状态转移实现,还跟上面的10 类似,我们使用一个二维 dp 数组,其中 dp[i][j] 表示 s中前i个字符组成的子串和p中前j个字符组成的子串是否能匹配。大小初始化为 (m+1) x (n+1),加1的原因是要包含 dp[0][0] 的情况(s和p都为空的话也是true).不同的在于星号的处理,这里星号可以出现在任意位置,前面字母不匹配直接就false了,*只是弥补缺失字母的情况。

    真正的业务规则:

   1 如果s[i]==p[j] or p[j]’==‘?’ 那么dp[i+1][j+1]=dp[i][j]

   2 p[j] =='*',那么dp[i][j]= dp[i][j - 1]|| dp[i-1][j]; 

还有考虑: s=“bcd” p="**cd" 这种连续* 的情况。

public static boolean isMatch(String s, String p) {
		    if (s == null || p == null) {
	            return false;
	        }
		    if(p.equals("*")){
		    	return true;
		    }
	        int m = s.length(), n = p.length();
	        boolean[][] dp = new boolean[m + 1][n + 1];
	        dp[0][0] = true;
		    for (int i = 1; i <= n; i++) { // 初始化第一行,p匹配s = ""
		    	 if (p.charAt(i-1) == '*' ) {
		                dp[0][i ] = dp[0][i-1 ];
		            }
		    }
		    for(int i=1;i<=m;i++){
		    	for(int j=1;j<=n;j++){
		    		// p[j - 1]不是"*"的情况,单字符匹配
		    		 if(s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '?'){
		    			       dp[i][j] = dp[i-1][j-1];
		    		 }
		    		 else if (p.charAt(j - 1) == '*') {
		    			  //依赖前面             
	                         dp[i][j] = dp[i][j - 1]|| dp[i-1][j];                      
	                           			 
		    		 }
		    	}
		    	
		    }
	        
		    return dp[m][n];
		}

 

Runtime: 13 ms, faster than 40.46% of Java online submissions for Wildcard Matching.

Memory Usage: 38.4 MB, less than 93.02% of Java online submissions forWildcard Matching.

时间复杂度 O(M*N)

 

2.2 递归

   递归更容易理解,但是递归本身效率不高,需要做优化才能通过测试。

下面的demo只是示意,跑简单的case,提交会报TLE错误。对于这样的case:"abbabaaabbabbaababbabbbbbabbbabbbabaaaaababababbbabababaabbababaabbbbbbaaaabababbbaabbbbaabbbbababababbaabbaababaabbbababababbbbaaabbbbbabaaaabbababbbbaababaabbababbbbbababbbabaaaaaaaabbbbbaabaaababaaaabb"
"**aa*****ba*a*bb**aa*ab****a*aaaaaa***a*aaaa**bbabb*b*b**aaaaaaaaa*a********ba*bbb***a*ba*bb*bb**a*b*bb"

  还是把当前两个元素所在s,p的位置分别为i, j。这个demo只是帮助翻译下题目。

 //递归	
	public static boolean isMatch(String s, String p) {
		return helper(s,p,0,0);
	}
	    //使用i,j 分别表示s,p的元素索引
		public static boolean helper(String s, String p,int i,int j) {
			if(i == s.length()){
				return  j == p.length() || (p.charAt(j) == '*' && helper(s, p, i, j + 1)) ; 
			}
			if (j == p.length() ) return false;
		  if (p.charAt(j) == '*') {
			 //跳过连续的'*'
			   while (j < p.length() && p.charAt(j) == '*') j++;  
	            if (j == p.length()) return true;
	            
	            while (i < s.length() && !helper(s, p,i, j)) ++i;

	            return i < s.length();
			}
		  else if (p.charAt(j) == s.charAt(i) || p.charAt(j) == '?') {
				return helper(s,p,i+1,j+1);
			}
		   return false;
	    }

要用递归实现这个题目,需要做深入的优化。这种时间复杂度是指数式阶乘那种。有优化到O(M*N)才行。

 

2.3  贪心算法

  上面的动态规划需要匹配完才知道结果,就是画图取的dp[m][n],有些case可能半路或者一开始就不匹配了,这样就没必要循环到底了。使用贪心算法,利用i,j指针分别标识两个字符串s,p的串首.开始进行匹配若i小于s串的长度,进行 while 循环。

  •   若当前两个字符相等,或着p中的字符是问号,则i和j分别加1。
  •  若 p[j] 是星号,那么我们要记录星号的位置,lp 赋为j,此时j再自增1,ls 赋为i。
  • 若当前 p[j] 不是星号,并且不能跟 p[i] 匹配上,那么此时就要靠星号了.如果没有出现过,直接就false了,如果出现过(就是ls>=0,初始化为-1),则利用*匹配。继续往下匹配,因为之后我们还要检查p串。前面说过*收位置限制,*之后的还要复合规则。
  • 处理连续* 的情况,j++;

 判断 j 指向串尾时 返回 true。 注意j 不要越界。

   //贪心算法
	public static boolean isMatch(String s, String p) {
		 int i = 0, j = 0;
	     // ls表示最近一个星号匹配到的s的位置
	     // lp表示最近一个星号的位置
	     int ls = -1, lp = -1;
		 while(i< s.length()){
			 if(j<p.length()&&( s.charAt(i)==p.charAt(j)||p.charAt(j)=='?')){
				 i++;
				 j++;
			 }
			 else if(j<p.length()&& p.charAt(j)=='*'){
				 ls =i;
				 lp = j++;
			 }
			
			 else if(ls>=0){
				 i =ls++;
				 j =lp+1;
			 }else{
				 return false;
			 }	 
		 }
		 
		//跳过连续*
		 while(j< p.length()&& p.charAt(j)=='*') j++;
		 
		 return j == p.length();
	}

Runtime: 2 ms, faster than 100.00% of Java online submissions for Wildcard Matching.

Memory Usage: 37.8 MB, less than 95.35% of Java online submissions forWildcard Matching.

时间复杂度:O(M*N)

 

字符串的题目,看似简单,实则复杂。而且适合用不同的方法求解,很考验人。本次的递归还没做出来。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值