0010. Regular Expression Matching (H)
Regular Expression Matching (H)
Given an input string (s
) and a pattern (p
), implement regular expression matching with support for '.'
and '*'
.
'.' Matches any single character.
'*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Note:
s
could be empty and contains only lowercase lettersa-z
.p
could be empty and contains only lowercase lettersa-z
, and characters like.
or*
.
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
题意
实现一个包含'.'、'*'的正则匹配判定。
思路
递归法:
递归边界为,当p空时,若s非空则返回false,若s空则返回true。(反过来s空p非空时无法作为边界,因为如果s为空,p为 ".*",则同样满足匹配)。
当p的第二位不为 '*' 时,如果当前s和p的第一位匹配,那么只要递归判断isMatch(s.subString(1), p.subString(1))即可;
当p的第二位为 '*' 时,说明要对p的第一位字符进行多次匹配判断,这里可以分为两种情况 (记 '*' 前字符为x):1. s的第一位与x匹配,递归判断s第一位之后的子字符串是否与p匹配 (因为 "x*" 表示x可以出现0次或多次,所以每次递归可以理解为去除了一个匹配的x);2. s的第一位与x不匹配,但与 '*' 后的第一个字符匹配,或者s的第一位既与x匹配,也与 '*' 后的第一个字符匹配,那么要判断s是否与 "x*" 之后的正则表达式相匹配。以上两种情况满足其一即可说明匹配成功。
记忆化搜索:
可以看到,在递归的过程中存在着许多重复的计算(即相同的s和p子串出现了多次),因此可以用一个数组将中间状态保存下来。
动态规划:
参考 [LeetCode] 10. Regular Expression Matching 正则表达式匹配
代码实现
Java
递归
class Solution {
public boolean isMatch(String s, String p) {
// 递归边界,只能以p空为判断点
if (p.isEmpty()) {
return s.isEmpty();
}
boolean isFirstMatch = (!s.isEmpty() && (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.'));
if (p.length() >= 2 && p.charAt(1) == '*') {
return ((isFirstMatch && isMatch(s.substring(1), p)) || isMatch(s, p.substring(2)));
} else {
return isFirstMatch && isMatch(s.substring(1), p.substring(1));
}
}
}
记忆化搜索优化
class Solution {
public boolean isMatch(String s, String p) {
return isMatch(s, 0, p, 0, new int[s.length() + 1][p.length() + 1]);
}
private boolean isMatch(String s, int sHead, String p, int pHead, int[][] record) {
if (pHead == p.length()) {
return sHead == s.length();
}
if (record[sHead][pHead] != 0) {
return record[sHead][pHead] == 1 ? true : false;
}
boolean matched = false;
boolean firstMatch = sHead < s.length() && (s.charAt(sHead) == p.charAt(pHead) || p.charAt(pHead) == '.');
if (pHead < p.length() - 1 && p.charAt(pHead + 1) == '*') {
matched = firstMatch && isMatch(s, sHead + 1, p, pHead, record) || isMatch(s, sHead, p, pHead + 2, record);
} else {
matched = firstMatch && isMatch(s, sHead + 1, p, pHead + 1, record);
}
record[sHead][pHead] = matched ? 1 : -1;
return matched;
}
}
动态规划
class Solution {
public boolean isMatch(String s, String p) {
boolean dp[][] = new boolean[s.length() + 1][p.length() + 1];
dp[0][0] = true;
for (int i = 0; i <= s.length(); i++) {
for (int j = 1; j <= p.length(); j++) {
if (j > 1 && p.charAt(j - 1) == '*') {
dp[i][j] = dp[i][j - 2] || i > 0 && dp[i - 1][j] && (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.');
} else {
dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.');
}
}
}
return dp[s.length()][p.length()];
}
}
JavaScript
/**
* @param {string} s
* @param {string} p
* @return {boolean}
*/
var isMatch = function (s, p) {
if (p.length === 0) {
return s.length === 0
}
let firstMatch = s.length !== 0 && (s[0] === p[0] || p[0] === '.')
if (p.length >= 2 && p[1] === '*') {
return (firstMatch && isMatch(s.slice(1), p)) || isMatch(s, p.slice(2))
} else {
return firstMatch && isMatch(s.slice(1), p.slice(1))
}
}