c++正则通配符问题

3 篇文章 0 订阅

前言

        正则表达式是一个非常有强力的工具,其中点号[.]可以匹配任意一个字符,星号[*]可以让之前的那个字符重复任意次数(包括0次)。

一、题目

        给我们输入两个字符串s和字符串p, s代表文本,p代表模式串,请你判断模式串p是否可以匹配文本s.我们可以假设模式串只包含小写字母和两种通配符且一定合法。
        函数签名如下:
        bool isMatch(string s , string p);

二、思路分析

        正则表达式算法问题只需要把住一个基本点:看两个字符是否匹配,一切逻辑围绕匹配/不匹配两个种情况张开即可。
        如果不考虑*通配符,面对两个待匹配字符s[i] 和 p[j], 我们唯一能做的就是看他们是否匹配:

bool isMatch(string s, string p) {
	int i = 0, j = 0;
	while (i < s.size() && j < p.size()) {
		// 点号[.]可以匹配任意一个字符
		if (s[i] == p[j] || p[j] == '.') {
			++i;
			++j;
		} else {
			return false;
		}
	}
	return i == j;
}

        如果加入[*]通配符,局面就会稍微复杂一些,不过只要分情况来分析,也不难理解。

  1. 如果匹配,即 s[i] == p[j], 那么两种情况:
            一种是 p[j] 有可能会匹配多个字符,比如 s = “aaa”, p = “a *” , 那么p[0] 会通过 * 匹配 3 个字符 “a”。
            另一种是p[j] 有可能回匹配0个字符,比如s = “aa” , p = “a * aa”, 由于后面的字符可以匹配s, 所以p[0] 只能匹配 0 次。
  2. 如果不匹配,即s[i] != p[j] ,只有一种情况:p[j] 只能匹配0次,然后看下一个字符是否能和 s[i] 匹配。比如说 s = “aa”, p = “b*aa”,此时p[0]只能匹配0次。
if (s[i] == p[j] || p[j] == '.') {
	// 匹配
	if (j < p.size()-1 && p[j + 1] == '*') {
		// 有*通配符,可以匹配0次或者多次
	} else {
		// 无*通配符,老老实实匹配1次
		++i;
		++j;
	}
} else {
	// 不匹配
	if (j < p.size()-1 && p[j + 1] == '*') {
		// 有*通配符,只能匹配0次
	} else {
		return false;
	}
}

三、动态规划解法

        由上面可以看出,这就是一个做选择的问题,要把所有可能的选择都穷举一遍才能得出结果。动态规划算法的核心就是状态选择状态就是i和j两个指针的位置,选择就是p[j] 选择匹配几个字符。
        根据状态,我们可以定义一个dp函数:
                bool dp(string &s, int i, string &p, int j)
        dp函数的定义:若dp(s,i,p,j) = true, 则表示s[i…] 可以匹配p[j…];若dp(s,i,p,j) = false,则表示s[i…]无法匹配p[j…]。
        根据这个定义,我们需要的答案就是 i = 0, j = 0时dp函数的结果,所以可以这样使用这个dp函数:

bool isMatch(string s, string p) {
	// 指针 i j 从索引0开始移动
	return dp(s,0,p,0);
}

        可以根据之前的代码写dp函数的主要逻辑:

bool dp(string& s, int i, string& p, int j) {

	if (s[i] == p[j] || p[j] == '.') {
		// 匹配
		if (j < p.size()-1 && p[j+1] == '*') {
			//  通配符匹配 0 次 或 多次
			return dp(s,i,p,j+2) || dp(s,i+1,p,j);
		} else {
			// 常规匹配 1次
			return dp(s,i+1,p,j+1);
		}
	} else {
		// 不匹配
		if (j < p.size()-1 && p[j+1] == '*') {
			// 通配符匹配 0 次
			return dp(s,i,p,j+2);
		} else {
			// 无法继续匹配
			return false;
		}

	}
}

        dp函数中base case:一个 base case 是 j == p.size()时, 按照dp函数的定义,这意味着模式串p已经被匹配完了,那么应该看看文本串s匹配到哪里了,如果s也恰好被匹配完,则说明匹配成功:

if (j == p.size()) {
	return i == s.size();
}

        另一个base case 是 i == s.size() 时,按照dp函数的定义,这种情况就意味着文本串s已经全部被匹配了,那么是不是只要简单地检查一下p是否也匹配完就行了呢?

if (i == s.size()) {
	// 这样行吗
	return j == p.size();
}

        这是不正确的, 此时并不能根据 j 是否完成匹配,只要p[j…] 能够匹配空串,就可以算完成匹配。比如 s = “a”, p = “abc”,当i 走到 s末尾的时候, j 并没有走到p的末尾,但 p 依然可以匹配 s.

	int m = s.size(), n = p.size();

	// base case
	if (i == m) {
		// 如果能匹配空串,一定是字符和*成对出现
		if ((n - j) % 2 == 1) {
			return false;
		}
		// 检查是否为 x*y*z*这种形式
		for (; j + 1 < n; j += 2) {
			if (p[j + 1] != '*')
				return false;
		}
		return true;
	}

        完整代码如下:

bool dp(string& s, int i, string& p, int j) {
	int m = s.size(), n = p.size();

	// base case
	if (j == n) {
		return i == m;
	}

	if (i == m) {
		// 如果能匹配空串,一定是字符和*成对出现
		if ((n - j) % 2 == 1) {
			return false;
		}
		// 检查是否为 x*y*z*这种形式
		for (; j + 1 < n; j += 2) {
			if (p[j + 1] != '*')
				return false;
		}
		return true;
	}

	// 记录状态(i,j), 消除重叠子问题
	string key = to_string(i) + "," + to_string(j);
	if (memo.count(key))
		return memo[key];

	bool res = false;


	if (s[i] == p[j] || p[j] == '.') {
		// 匹配
		if (j < p.size()-1 && p[j+1] == '*') {
			//  通配符匹配 0 次 或 多次
			return dp(s,i,p,j+2) || dp(s,i+1,p,j);
		} else {
			// 常规匹配 1次
			return dp(s,i+1,p,j+1);
		}
	} else {
		// 不匹配
		if (j < p.size()-1 && p[j+1] == '*') {
			// 通配符匹配 0 次
			return dp(s,i,p,j+2);
		} else {
			// 无法继续匹配
			return false;
		}
	}

	// 将当前结果记入备忘录
	memo[key] = res;

	return res;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值