44通配符匹配(动态规划法、贪心回溯法、递归法)

1、题目描述

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。

'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。

说明:

s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

2、示例

输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

输入:
s = "acdcb"
p = "a*c?b"
输入: false

3、题解

解法一 动态规划法

基本思想:动态规划法,所有字符串匹配问题都可以用动态规划法,利用已匹配的信息计算未匹配的信息。

  • dp[i][j]表示s[0,i-1]前i个字符与p[0,j-1]前j个字符是否匹配
  • 初始化:当s和p都为空时匹配成功;当s不为空p为空时不匹配;当s为空j开头都为*,*可以匹配空串,开头都能匹配成功。
  • 两重循环给二维数组dp赋值
  • 若当前p[j]为*,如果*算作空串则s前i个串与p前j个串是否匹配取决于s前i个串p前j-1个串是否匹配;如果*匹配当前s[i]字符则s前i个串与p前j个串是否匹配取决于s前i-1个串p前j个串是否匹配。
  • 若当前s[i]字符与当前p[j]字符相等,或者当前p[j]为?,s前i个串与p前j个串是否匹配取决于s前i-1个串p前j-1个串是否匹配
  • 上述情况都不满足,s前i个串与p前j个串不匹配

解法二 贪心回溯法

  • 初始化标记点为-1,如果标记点大于等于0,说明有标记才能回溯
  • 一重循环对s每个字符尝试匹配
  • 当前s[i]字符与当前p[j]字符相等,s和p的下标都后移一位
  • 当前p[j]为*,标记当前s中字符的位置,标记p中*所在位置为flag_j,j后移一位到*的下一个字符,说明*匹配空串
  • 如果*匹配空串后,p中*的后一位与标记处的s字符不匹配,说明*不能匹配空串,*匹配s当前字符,i后移一位,j继续指向*的下一个字符
  • 如果上述都不匹配,返回false匹配不成功,可能是p到头了而s中还有字符,可能是当前s所指字符和p所指字符不相等
  • s已经到头了,去除p后多余的*,如果最后j能到p的头说明匹配成功,否则说明不匹配成功除了*还多了其他字符

解法三 递归法

基本思想:递归法,尽管有优化还是超时,但这是一种思想记录一下。

  • 剪枝优化,去除重复的*
  • 如果s的下标m和p的下标n都已经到头,说明匹配成功
  • 如果s的下标m到头p的下标n没有到头,且p剩余字符不是*,说明匹配不成功
  • 如果s的下标m到头p的下标n没有到头,且p下一字符是*,继续递归调用看剩余字符是否是*
  • 如果s的下标m没有到头p的下标n到头,说明匹配不成功
  • 如果当前s所指字符和p所指字符相同,递归调用s和p下标都+1
  • 如果p所指字符为*,则可能有三种匹配情况,*匹配空字符串,*匹配多个字符,*匹配完当前字符不再匹配
  • 如果当前p所指字符为?,递归调用s和p下标都+1
  • 如果当前s所指字符和p所指字符不相等,说明匹配不成功
  • 最后一定要返回false,因为上面进入if后调用Recursion,但如果成功返回true但如果不成功,会退出if执行下一条语句,而这个bool函数没有语句运行了,返回的是随机数
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
	bool isMatch(string s, string p) {
		//简化版:动态规划
		vector<vector<bool>> dp(s.size()+1,vector<bool>(p.size()+1,false));
		dp[0][0]=true;
		for(int i=1;i<=p.size();i++)
		{
			if(p[i-1]=='*')
				dp[0][i]=true;
			else
				break;
		}
		for(int i=1;i<=s.size();i++)
		{
			for(int j=1;j<=p.size();j++)
			{
				if(p[j-1]=='?'||s[i-1]==p[j-1])
					dp[i][j]=dp[i-1][j-1];
				else if(p[j-1]=='*')
					dp[i][j]=dp[i][j-1]||dp[i-1][j];
			}
		}
		return dp[s.size()][p.size()];
	}
};
class Solution1 {
public:
	bool isMatch(string s, string p) {
		//基本思想:递归法,尽管有优化还是超时,但这是一种思想记录一下
		string p1;
		int i = 0;
		//剪枝优化,去除重复的*
		p1 = Remove_stars(p);
		//递归调用
		return Recursion(s, p1, 0, 0);
	}
	//剪枝优化,去除重复的*
	string Remove_stars(string &p)
	{
		string p1;
		int i;
		if (p.size() == 0)
			return "";
		while (i < p.size() - 1)
		{
			if (p[i] == '*' && p[i + 1] == '*')
			{
				i++;
				continue;
			}
			else
				p1 += p[i];
			i++;
		}
		p1 += p[p.size() - 1];
		return p1;
	}
	bool Recursion(string s, string p, int m, int n)
	{
		//如果s的下标m和p的下标n都已经到头,说明匹配成功
		if (m == s.size() && n == p.size())
			return true;
		//如果s的下标m到头p的下标n没有到头,且p剩余字符不是*,说明匹配不成功
		if (m == s.size() && n < p.size() && p[n] != '*')
			return false;
		//如果s的下标m到头p的下标n没有到头,且p下一字符是*,继续递归调用看剩余字符是否是*
		if (m == s.size() && n < p.size() && p[n] == '*')
		{
			return Recursion(s, p, m, n + 1);
		}
		//如果s的下标m没有到头p的下标n到头,说明匹配不成功
		if (m < s.size() && n == p.size())
			return false;
		//如果当前s所指字符和p所指字符相同,递归调用s和p下标都+1
		if (s[m] == p[n])
		{
			if (Recursion(s, p, m + 1, n + 1))
				return true;
		}
		//如果p所指字符为*,则可能有三种匹配情况,*匹配空字符串,*匹配多个字符,*匹配完当前字符不再匹配
		else if (p[n] == '*')
		{
			if (Recursion(s, p, m, n + 1))
				return true;
			if (Recursion(s, p, m + 1, n))
				return true;
			if (Recursion(s, p, m + 1, n + 1))
				return true;
		}
		//如果当前p所指字符为?,递归调用s和p下标都+1
		else if (p[n] == '?')
		{
			if (Recursion(s, p, m + 1, n + 1))
				return true;
		}
		//如果当前s所指字符和p所指字符不相等,说明匹配不成功
		else if (s[m] != p[n])
		{
			return false;
		}
		//最后一定要返回false,因为上面进入if后调用Recursion,但如果成功返回true但如果不成功,会退出if执行下一条语句,而这个bool函数没有语句运行了,返回的是随机数
		return false;
	}
};
class Solution2 {
public:
	bool isMatch(string s, string p) {
		//基本思想:动态规划法,所有字符串匹配问题都可以用动态规划法,利用已匹配的信息计算未匹配的信息。
		int m = s.size(), n = p.size(), i, j;
		//dp[i][j]表示s[0,i-1]前i个字符与p[0,j-1]前j个字符是否匹配
		vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
		//当s和p都为空时匹配成功
		dp[0][0] = true;
		//当s不为空p为空时不匹配
		for (i = 1; i < m; i++)
			dp[i][0] = false;
		//当s为空j开头都为*,*可以匹配空串,开头都能匹配成功
		for (j = 0; j < n; j++)
		{
			if (p[j] == '*')
				dp[0][j + 1] = true;
			else
				break;
		}
		//两重循环给二维数组dp赋值
		for (i = 0; i < m; i++)
		{
			for (j = 0; j < n; j++)
			{
				//若当前p[j]为*,如果*算作空串则s前i个串与p前j个串是否匹配取决于s前i个串p前j-1个串是否匹配
				//如果*匹配当前s[i]字符则s前i个串与p前j个串是否匹配取决于s前i-1个串p前j个串是否匹配
				if (p[j] == '*')
				{
					dp[i + 1][j + 1] = dp[i + 1][j] || dp[i][j + 1];
				}
				//若当前s[i]字符与当前p[j]字符相等,或者当前p[j]为?,s前i个串与p前j个串是否匹配取决于s前i-1个串p前j-1个串是否匹配
				else if (s[i] == p[j] || p[j] == '?')
				{
					dp[i + 1][j + 1] = dp[i][j];
				}
				//上述情况都不满足,s前i个串与p前j个串不匹配
				else
					dp[i + 1][j + 1] = false;
			}
		}
		return dp[m][n];

	}
};
class Solution3 {
public:
	bool isMatch(string s, string p) {
		//基本思想:贪心回溯法
		int m = s.size(), n = p.size(), i, j, flag_i, flag_j;
		i = 0;
		j = 0;
		//初始化标记点为-1,如果标记点大于等于0,说明有标记才能回溯
		flag_j = -1;
		flag_i = -1;
		//一重循环对s每个字符尝试匹配
		while (i < m)
		{
			//当前s[i]字符与当前p[j]字符相等,s和p的下标都后移一位
			if (j < n && (s[i] == p[j] || p[j] == '?'))
			{
				i++;
				j++;
			}
			//当前p[j]为*,标记当前s中字符的位置,标记p中*所在位置为flag_j,j后移一位到*的下一个字符,说明*匹配空串
			else if (j < n && p[j] == '*')
			{

				flag_i = i;
				flag_j = j;
				j++;
			}
			//如果*匹配空串后,p中*的后一位与标记处的s字符不匹配,说明*不能匹配空串,*匹配s当前字符,i后移一位,j继续指向*的下一个字符
			else if (flag_i >= 0)
			{
				flag_i++;
				i = flag_i;
				j = flag_j + 1;
			}
			//如果上述都不匹配,返回false匹配不成功,可能是p到头了而s中还有字符,可能是当前s所指字符和p所指字符不相等
			else
				return false;
		}
		//s已经到头了,去除p后多余的*,如果最后j能到p的头说明匹配成功,否则说明不匹配成功除了*还多了其他字符
		while (j < n && p[j] == '*')
			j++;
		return j==n;

	}
};
int main()
{
	Solution2 solute;
	string s = "aa";
	string p = "*";
	cout << solute.isMatch(s, p) << endl;
	return 0;
}

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值