题目描述
LeetCode原题链接:10. Regular Expression Matching
Given an input string s
and a pattern p
, implement regular expression matching with support for '.'
and '*'
where:
-
'.'
Matches any single character. -
'*'
Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Example 1:
Input: s = "aa", p = "a" Output: false Explanation: "a" does not match the entire string "aa".
Example 2:
Input: s = "aa", p = "a*" Output: true Explanation: '*' means zero or more of the preceding element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".
Example 3:
Input: s = "ab", p = ".*" Output: true Explanation: ".*" means "zero or more (*) of any character (.)".
Example 4:
Input: s = "aab", p = "c*a*b" Output: true Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore, it matches "aab".
Example 5:
Input: s = "mississippi", p = "mis*is*p*." Output: false
Constraints:
1 <= s.length <= 20
1 <= p.length <= 30
-
s
contains only lowercase English letters. -
p
contains only lowercase English letters,'.'
, and'*'
. - It is guaranteed for each appearance of the character
'*'
, there will be a previous valid character to match.
解法:动态规划(c++版)
解法参考 jianchao-li 的帖子
1 class Solution { 2 public: 3 bool isMatch(string s, string p) { 4 int m = s.size(), n = p.size(); 5 vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false)); 6 dp[0][0] = true; 7 for(int i = 0; i <= m; i++) { 8 for(int j = 1; j <= n; j++) { 9 if(p[j - 1] == '*') { 10 dp[i][j] = dp[i][j - 2] || ( i > 0 && dp[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == '.')); 11 } 12 else { 13 dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.'); 14 } 15 } 16 } 17 return dp[m][n]; 18 } 19 };
思路分析
我们都知道动态规划的核心是找到转移方程:当前状态和上一状态之间的关系。 对于这道题目,我们要先搞清楚两个符号的匹配规则:
- '.' 号相当于赖子,可以替换任意一个字符;
- '*' 号需和它前面一个字符配合使用,代表前一个字符重复出现n次(n可以为0,即可以起到消除前一个字符的作用)
jianchao-li的解析刚看有点懵逼,不是很好懂。其实核心思路是用 pattern 去逐位匹配 input string,主要是分为3种情况:
(1)pattern遍历到的字符不是 '*' 时,比较 pattern 和 string「相同位置」的字符是否相等,相等成立的条件是 1-均是相同的字母;2-pattern中对应位置的字符是赖子 '.'
(2)pattern遍历到的字符是 '*' 时,根据 '*' 号的两种作用,又细分为两种情况:
- 消除前一个字符的作用:被消除的字符「之前」的 pattern 是否和「当前位置」的 string 匹配
- 重复前一个字符n次的作用:string 「当前位置」的字符是否和 '*' 号「前一个」字符相等,相等成立的条件是 1-均是相同的字母;2-pattern中对应位置的字符是赖子 '.'
我们用两个指针 i 和 j 分别遍历 input string 和 pattern,注意上面「」中加粗的内容,这关系到条件判断中需要判断的字符or字符串的下标索引位置。当前遍历到的位置的 string 和 pattern 是否能match,取决于上一状态true && 当前位置true。再用表格梳理一下:
代码分析
再回过头来看看 jianchao-li 的帖子,相信开头的这句话 We define dp[i][j]
to be true
if s[0..i)
matches p[0..j)
and false
otherwise. 肯定给很多人造成了困惑。其实这里前开后闭的区间范围设置遵循了编程中对字符串处理的通用准则:i、j表示结束的位置但是不包含这个位置的字符。可以回忆一下 string::erase(iterator first, iterator last)这个库函数入参的 first 和 last 的含义,是不是一个道理呢?
我们也可以理解为dp[i][j]表示的是string[0, i-1]位置的子串是否和pattern[0, j-1]位置的子串相匹配。
举个