老妈子级别大白话从零讲解LeetCode 10. Regular Expression Marching (HOT 100)

这两天翻了很多题解,思路讲解方面总是要么不够清楚要么有些许漏洞,不易于像我这样的新手理解;现在我终于弄明白解题思路了,详细整理一下,希望能让更多的朋友少走弯路。

题目解析

'.' Matches any single character.
'*' Matches zero or more of the preceding element.

s只能包含英文字母,p只能包含英文字母和'.','*';都可以为空
保证每次'*'出现前面必定有一个有效的可以用来匹配的字母元素
(不能出现连续的`'*'`)
目标是p的所有元素经过'*'和'.'处理之后与s相同

函数原型为:
bool isMatch(const char *s, const char *p)

举几个例子:
isMatch("aa","a") → false <- "a" does not match the entire string "aa"
isMatch("aa","aa") → true <- "aa" matches the entire "aa"
isMatch("aa", "a*") → true <- "*" means matches one or more preceding chars, 'a'*2 == "aa"
isMatch("aa", ".*") → true <- '.' 等于任意字符,'.*'指matches任意字符任意次数
isMatch("aab", "c*a*b") → true <- 第一个'*'matches zero char, 第二个'*'matches "aa"
isMatch("aaaa","ab*a*c*a") → true <- 第一个'*' matches zero char,  第二个'*'matches'a', "aa", "aaa", 也可以matches "aaaa", 根据第三个'*'判断第二个'*'应该对应的是"***"

递归解法

分析

因为*号最复杂,甚至可以对应零次,所以分成两种情况分别考虑:p[1] == '*'p[1] != '*';
对比指对s和p的首字母进行比较:(s != "" && (s[0]==p[0] || '.'==p[0]))

  1. p[1] != '*'时, 说明当前的p[0]不可以对应0到n个字符,所以直接对比就可以,剩下的交给递归
  2. 判等:s[0] == p[0] || p[0] == '.'
  3. 递归判断 isMatch(s.substr(1), p.substr(1))两个子串
  4. p[1] == *时,说明当前p[0]可以对应0到n个字符(会发生0到n次的变化,而且都是有效的
  5. 假设只对应0个字符(假设发生0次):直接跳过 p[0], p[1],进行递归 isMatch(s, p.substr(2))
  6. 假设对应1个以上的字符(假设发生1次以上):需要先判断是否相等,然后用 s.substr(1)进行递归: (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p)

代码实现

class Solution(object){
	bool isMatch(string s, string p){
		if (p == ""){
			return s == "";
		}
		if (p.size() > 1 && p[1] == '*'){
			// Remember: not > and > or
			return isMatch(s, p.substr(2)) || (s != "" && (s[0] == p[0] || '.' == p[0])) && isMatch(s.substr(1), p);
		} else {
			return (s != "" && (s[0] == p[0] || '.' == p[0])) && isMatch(s.substr(1), p.substr(1));
		}
	}
};

DP解法

dp[i][j]指s的前i个元素是否能被p的前j个元素匹配

思路整理

状态转移方程

已知dp[i-1][j-1],也就是当前面的子串都已经匹配时,需要直到下一个元素的情况(s[i], p[j]),就是找出dp[i][j]的值。

分情况讨论:

  1. 最简单的情况,s[i] == p[j]-> dp[i][j] = dp[i-1][j-1]
  2. p[j]所有可能的值来考虑:
    1. p[j] == '.'->dp[i][j] = dp[i-1][j-1]
    2. p[j] == '*'->…?(这部分真的难TT 现学动态规划、瞅了十几篇题解才弄清楚hhh)

怎么区分p[j] == '*'的几种情况?

分析:

  • '*'的作用是匹配零个或几个它前面的元素p[j-1]
  • 形如 任意字母+'*' 的子串对匹配结果没有影响。
  • s的前一个元素能匹配上它才有用,不然'*'也无能为力,只能让前一个字符消失,也就是匹配前一个字符零次。整个字符串能否匹配成功取决于去掉'*'p[j-1]后的子串。
  • 换句话来说,p[j] == '*'时,dp[i][j]只取决于dp[i][j-2],与dp[i][j-1]无关
  • 如果只有s[i] == p[j-1],匹配能否成功取决于dp[i-1][j-2],如果后者为true,前者也一定正确;如果后者返回false,整个字符串匹配失败。所以不需要考虑只有一个元素匹配成功的情况,直接令dp[i][j] = dp[i][j-2]就可以了。

所以得出下面几种情况:

  1. s[i] == p[j-1]或者p[j-1] == '.'时:
    1. 能匹配零个元素:dp[i][j] = dp[i][j-2]
    2. 能匹配多个元素:dp[i][j] = dp[i-1][j]
      dp[i][j]进行赋值时,先检查能不能匹配零个元素,再检查能不能匹配多个元素(使用 ||操作符)
  2. s[i] != p[j-1]或者p[j-1] != '.'时:与能匹配零个元素结果相同

代码实现

  • 实际上dp这个二维数组的容量比s和p大一圈,因为要处理s和p都为空的情况, dp的行标i对应s[i],列标j对应p[j],循环从1开始;
  • 初始化我用了力扣里一位深藏不露的大神的方法,只初始化dp[0][0]true,用memset()初始化剩下的值为false
bool isMatch(string s, string p){
	s = ' ' + s;
	p = ' ' + p;
	int m = s.size(), n = p.size();
	bool dp[m+1][n+1];
	memset(dp, false, (m+1) * (n+1));
    dp[0][0] = true;
	
	for (int i = 1; i <= m; ++i){
		for (int j = 1; j <= n; ++j){
			if (s[i-1] == p[j-1] || p[j-1] == '.'){
				dp[i][j] = dp[i-1][j-1];
			} 
			if (p[j-1] == '*'){
				if (s[i-1] == p[j-2] || p[j-2] == '.'){				
                    dp[i][j] = dp[i-1][j] || dp[i][j-2];  
					// 如果dp[i-1][j] == true,则等号左边赋值为dp[i-1][j]
				} else {
                    dp[i][j] = dp[i][j-2];
                }
			} // 因为初始化为假,所以不满足为真条件时不用进行额外操作		
		}
	}
    return dp[m][n];
}

冲鸭!

你可能感兴趣的:(#,DS,&,Algo,Daily,算法,动态规划,leetcode,c++)