LeetCode044——通配符匹配

我的LeetCode代码仓:https://github.com/617076674/LeetCode

原题链接:https://leetcode-cn.com/problems/wildcard-matching/description/

题目描述:

LeetCode044——通配符匹配_第1张图片

知识点:递归、动态规划

思路一:递归实现(在LeetCode中提交会超时)

本题和LeetCode010——正则表达式匹配很像,但又有所不同。在LeetCode010——正则表达式匹配中:'.'匹配任意单个字符,'*'匹配零个或多个前面的元素。而在本题中,'?'匹配任意单个字符,'*'可以匹配任意字符串,包括空字符串。虽然匹配条件发生了改变,但解题方法是不变的。

递归终止条件

(1)当字符串p的长度为0时,若字符串s的长度也为0,返回true。若字符串s的长度不为0,返回false。

(2)当字符串s的长度为0时,如果此时字符串p中的字符全是'*',返回true,否则,返回false。

递归过程

(1)如果字符串s的最后一个字符能与字符串p的最后一个字符相匹配,我们递归地去判断字符串s去除最后一个字符后的字符串能否与字符串p去除最后一个字符后的字符串相匹配。

(2)如果字符串p的最后一个字符是'*',我们需要去判断字符串s能否与字符串p去除最后一个字符后的字符串相匹配、字符串s去除最后一个字符后的字符串能否与字符串p去除最后一个字符后的字符串相匹配、字符串s去除最后两个字符后的字符串能否与字符串p去除最后一个字符后的字符串相匹配、……、空字符串能否与字符串p去除最后一个字符后的字符串相匹配。只要这其中有一个能匹配,就应该返回true。如果都不能匹配,返回false。

(3)如果不满足前面两种情况,说明字符串s的最后一个字符不能与字符串p的最后一个字符相匹配,直接返回false。

这个思路的时间复杂度比较复杂,最差情况可能是O(ns * np)级别的,其中ns为字符串s的长度,np为字符串p的长度。空间复杂度就是递归深度,最差情况也是O(ns * np)级别的。

JAVA代码:

public class Solution {
	
    //recursion realization
	public boolean isMatch(String s, String p) {
        int ns = s.length();
        int np = p.length();
        if(np == 0) {
        	return ns == 0;
        }
        if(ns == 0) {
        	for (int i = 0; i < np; i++) {
				if(p.charAt(i) != '*') {
					return false;
				}
			}
        	return true;
        }
        if(p.charAt(np - 1) == '?' || s.charAt(ns - 1) == p.charAt(np - 1)) {
        	return isMatch(s.substring(0, ns - 1), p.substring(0, np - 1));
        }
        if(p.charAt(np - 1) == '*') {
        	for (int i = 0; i <= ns; i++) {
				if(isMatch(s.substring(0, i), p.substring(0, np - 1))) {
					return true;
				}
			}
        }
        return false;
    }
}

思路二:动态规划

状态定义:f(x, y)------字符串s中[0, x - 1]范围内的字符串能否匹配字符串p中[0, y - 1]范围内的字符串

状态转移

(1)如果p(y) == '?', f(x, y) = f(x - 1, y - 1)。

(2)如果p(y) == s(x), f(x, y) = f(x - 1, y - 1)。
(3)如果p(y) == '*',f(x, y) = f(x, y - 1) || f(x - 1, y - 1) || f(x - 2, y - 1) || ... || f(0, y - 1)。
时间复杂度是O(ns * np)级别的,其中ns为字符串s的长度,np为字符串p的长度。空间复杂度也是O(ns * np)级别的。

JAVA代码:

public class Solution {

	//dynamic programming realization
	public boolean isMatch(String s, String p) {
		int ns = s.length();
        int np = p.length();
		boolean[][] matched = new boolean[ns + 1][np + 1];
		matched[0][0] = true;
		for (int i = 1; i < np + 1; i++) {
			matched[0][i] = true;
			for (int j = 0; j <= i - 1; j++) {
				if(p.charAt(j) != '*') {
					matched[0][i] = false;
				}
			}
		}
		for (int i = 1; i < matched.length; i++) {
			for (int j = 1; j < matched[0].length; j++) {
				if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
					matched[i][j] = matched[i - 1][j - 1];
				}else if(p.charAt(j - 1) == '*') {
					for (int k = 0; k <= i; k++) {
						if(matched[i][j]) {
							break;
						}
						matched[i][j] = matched[i][j] || matched[k][j - 1];
					}
				}
			}
		}
		return matched[ns][np];
	}
}

LeetCode解题报告:

LeetCode044——通配符匹配_第2张图片

思路三:对思路一的改进(在LeetCode中提交会超时)

在递归过程中,如果字符串p的最后一个字符是'*',我们只需要去判断字符串s能否与字符串p去除最后一个字符后的字符串相匹配或者字符串s去除最后一个字符后的字符串能否与字符串p相匹配。这里的分类方法变成了不使用该最后一个字符'*'去匹配或者使用该最后一个字符'*'去匹配。具体的原理分析见思路四。

JAVA代码:

public class Solution {
	
	//recursion realization2
	public boolean isMatch(String s, String p) {
        int ns = s.length();
        int np = p.length();
        if(np == 0) {
        	return ns == 0;
        }
        if(ns == 0) {
        	for (int i = 0; i < np; i++) {
				if(p.charAt(i) != '*') {
					return false;
				}
			}
        	return true;
        }
        if(p.charAt(np - 1) == '?' || s.charAt(ns - 1) == p.charAt(np - 1)) {
        	return isMatch(s.substring(0, ns - 1), p.substring(0, np - 1));
        }
        if(p.charAt(np - 1) == '*') {
        	return isMatch(s.substring(0, ns - 1), p.substring(0, np)) || isMatch(s.substring(0, ns), p.substring(0, np - 1));
        }
        return false;
    }
}

思路四:对思路二的改进

状态定义:f(x, y)------字符串s中[0, x - 1]范围内的字符串能否匹配字符串p中[0, y - 1]范围内的字符串

状态转移

(1)如果p(y) == '?', f(x, y) = f(x - 1, y - 1)。

(2)如果p(y) == s(x), f(x, y) = f(x - 1, y - 1)。
(3)如果p(y) == '*',f(x, y) = f(x, y - 1) || f(x - 1, y)。

为什么可以把思路二中的f(x, y) = f(x, y - 1) || f(x - 1, y - 1) || f(x - 2, y - 1) || ... || f(0, y - 1)简化为f(x, y) = f(x, y - 1) || f(x - 1, y)呢?

以s="acdcb",p="a*c?b"为例进行分析。其动态规划的matched表如下所示。

  0("") 1("a") 2("a*") 3("a*c") 4("a*c?") 5("a*c?b")
0("") × × × × ×
1("a") × × × ×
2("ac") × × × ×
3("acd") × × × ×
4("acdc") × × × ×
5("acdcb") × × × ×

对于其中第2列,其代表的p串是"a*",是*结尾的。我们计算"a"能否与"a*"匹配时,根据思路二,我们看的是""能否与"a"相匹配以及"a"能否与"a"相匹配。而根据思路四,我们看的是"a"能否与"a"相匹配以及""能否与"a*"相匹配。而我们看""能否与"a*"相匹配时最终看的也是""能否与"a"相匹配,因此两种情况的参照其实是相同的,我们最终看的是""能否与"a"相匹配以及"a"能否与"a"相匹配。对于第2列中的其余行的分析其实也是同理。说明思路二和思路四的最终逻辑其实是一样的,但是思路四比思路二的计算步骤要简洁很多。

从数学式子上分析,

f(x, y) = f(x, y - 1) || f(x - 1, y) = f(x, y - 1) || f(x - 1, y - 1) || f(x - 2, y)

          = f(x, y - 1) || f(x - 1, y - 1) || f(x - 2, y - 1) || f(x - 3, y) = ...

          = f(x, y - 1) || f(x - 1, y - 1) || f(x - 2, y - 1) || ... || f(1, y - 1) || f(0, y)

          = f(x, y - 1) || f(x - 1, y - 1) || f(x - 2, y - 1) || ... || f(1, y - 1) || f(0, y - 1)

这两者也是等价的。

时间复杂度是O(ns * np)级别的,其中ns为字符串s的长度,np为字符串p的长度。空间复杂度也是O(ns * np)级别的。

JAVA代码:

public class Solution {

	//dynamic programming realization2
	public boolean isMatch(String s, String p) {
		int ns = s.length();
        int np = p.length();
		boolean[][] matched = new boolean[ns + 1][np + 1];
		matched[0][0] = true;
		for (int i = 1; i < np + 1; i++) {
			matched[0][i] = true;
			for (int j = 0; j <= i - 1; j++) {
				if(p.charAt(j) != '*') {
					matched[0][i] = false;
				}
			}
		}
		for (int i = 1; i < matched.length; i++) {
			for (int j = 1; j < matched[0].length; j++) {
				if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
					matched[i][j] = matched[i - 1][j - 1];
				}else if(p.charAt(j - 1) == '*') {
					matched[i][j] = matched[i - 1][j] || matched[i][j - 1];
				}
			}
		}
		return matched[ns][np];
	}
}

LeetCode解题报告:

LeetCode044——通配符匹配_第3张图片

 

你可能感兴趣的:(LeetCode题解)