[LeetCode] 10. Regular Expression Matching 正则表达式匹配
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 precedeng 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
通配符匹配问题,和44. Wildcard Matching类似。'.'表示1个字符,'*'表示它前面的字符可以有0个,1个或是多个,比如:字符串a*b,可以表示b或是aaab,即a的个数可以是0个,也可以是多个。
解法1:DP
解法2: 递归
Java:
public boolean isMatch(String s, String p) { if (s == null || p == null) { return false; } boolean[][] dp = new boolean[s.length()+1][p.length()+1]; dp[0][0] = true; for (int i = 0; i < p.length(); i++) { if (p.charAt(i) == '*' && dp[0][i-1]) { dp[0][i+1] = true; } } for (int i = 0 ; i < s.length(); i++) { for (int j = 0; j < p.length(); j++) { if (p.charAt(j) == '.') { dp[i+1][j+1] = dp[i][j]; } if (p.charAt(j) == s.charAt(i)) { dp[i+1][j+1] = dp[i][j]; } if (p.charAt(j) == '*') { if (p.charAt(j-1) != s.charAt(i) && p.charAt(j-1) != '.') { dp[i+1][j+1] = dp[i+1][j-1]; } else { dp[i+1][j+1] = (dp[i+1][j] || dp[i][j+1] || dp[i+1][j-1]); } } } } return dp[s.length()][p.length()]; }
Java:
public boolean isMatch(String s, String p) { // base case if (p.length() == 0) { return s.length() == 0; } // special case if (p.length() == 1) { // if the length of s is 0, return false if (s.length() < 1) { return false; } //if the first does not match, return false else if ((p.charAt(0) != s.charAt(0)) && (p.charAt(0) != '.')) { return false; } // otherwise, compare the rest of the string of s and p. else { return isMatch(s.substring(1), p.substring(1)); } } // case 1: when the second char of p is not '*' if (p.charAt(1) != '*') { if (s.length() < 1) { return false; } if ((p.charAt(0) != s.charAt(0)) && (p.charAt(0) != '.')) { return false; } else { return isMatch(s.substring(1), p.substring(1)); } } // case 2: when the second char of p is '*', complex case. else { //case 2.1: a char & '*' can stand for 0 element if (isMatch(s, p.substring(2))) { return true; } //case 2.2: a char & '*' can stand for 1 or more preceding element, //so try every sub string int i = 0; while (i<s.length() && (s.charAt(i)==p.charAt(0) || p.charAt(0)=='.')){ if (isMatch(s.substring(i + 1), p.substring(2))) { return true; } i++; } return false; } }
Java:
public class Solution { public boolean isMatch(String s, String p) { if(p.length() == 0) return s.length() == 0; //p's length 1 is special case if(p.length() == 1 || p.charAt(1) != '*'){ if(s.length() < 1 || (p.charAt(0) != '.' && s.charAt(0) != p.charAt(0))) return false; return isMatch(s.substring(1), p.substring(1)); }else{ int len = s.length(); int i = -1; while(i<len && (i < 0 || p.charAt(0) == '.' || p.charAt(0) == s.charAt(i))){ if(isMatch(s.substring(i+1), p.substring(2))) return true; i++; } return false; } } }
Python:
# dp with rolling window class Solution: # @return a boolean def isMatch(self, s, p): k = 3 result = [[False for j in xrange(len(p) + 1)] for i in xrange(k)] result[0][0] = True for i in xrange(2, len(p) + 1): if p[i-1] == '*': result[0][i] = result[0][i-2] for i in xrange(1,len(s) + 1): if i > 1: result[0][0] = False for j in xrange(1, len(p) + 1): if p[j-1] != '*': result[i % k][j] = result[(i-1) % k][j-1] and (s[i-1] == p[j-1] or p[j-1] == '.') else: result[i % k][j] = result[i % k][j-2] or (result[(i-1) % k][j] and (s[i-1] == p[j-2] or p[j-2] == '.')) return result[len(s) % k][len(p)]
Python:
# dp # Time: O(m * n) # Space: O(m * n) class Solution2: # @return a boolean def isMatch(self, s, p): result = [[False for j in xrange(len(p) + 1)] for i in xrange(len(s) + 1)] result[0][0] = True for i in xrange(2, len(p) + 1): if p[i-1] == '*': result[0][i] = result[0][i-2] for i in xrange(1,len(s) + 1): for j in xrange(1, len(p) + 1): if p[j-1] != '*': result[i][j] = result[i-1][j-1] and (s[i-1] == p[j-1] or p[j-1] == '.') else: result[i][j] = result[i][j-2] or (result[i-1][j] and (s[i-1] == p[j-2] or p[j-2] == '.')) return result[len(s)][len(p)]
Python:
# iteration class Solution3: # @return a boolean def isMatch(self, s, p): p_ptr, s_ptr, last_s_ptr, last_p_ptr = 0, 0, -1, -1 last_ptr = [] while s_ptr < len(s): if p_ptr < len(p) and (p_ptr == len(p) - 1 or p[p_ptr + 1] != '*') and \ (s_ptr < len(s) and (p[p_ptr] == s[s_ptr] or p[p_ptr] == '.')): s_ptr += 1 p_ptr += 1 elif p_ptr < len(p) - 1 and (p_ptr != len(p) - 1 and p[p_ptr + 1] == '*'): p_ptr += 2 last_ptr.append([s_ptr, p_ptr]) elif last_ptr: [last_s_ptr, last_p_ptr] = last_ptr.pop() while last_ptr and p[last_p_ptr - 2] != s[last_s_ptr] and p[last_p_ptr - 2] != '.': [last_s_ptr, last_p_ptr] = last_ptr.pop() if p[last_p_ptr - 2] == s[last_s_ptr] or p[last_p_ptr - 2] == '.': last_s_ptr += 1 s_ptr = last_s_ptr p_ptr = last_p_ptr last_ptr.append([s_ptr, p_ptr]) else: return False else: return False while p_ptr < len(p) - 1 and p[p_ptr] == '.' and p[p_ptr + 1] == '*': p_ptr += 2 return p_ptr == len(p)
Python:
# recursive class Solution4: # @return a boolean def isMatch(self, s, p): if not p: return not s if len(p) == 1 or p[1] != '*': if len(s) > 0 and (p[0] == s[0] or p[0] == '.'): return self.isMatch(s[1:], p[1:]) else: return False else: while len(s) > 0 and (p[0] == s[0] or p[0] == '.'): if self.isMatch(s, p[2:]): return True s = s[1:] return self.isMatch(s, p[2:])
Python:
class Solution(object): def isMatch(self, s, p): # The DP table and the string s and p use the same indexes i and j, but # table[i][j] means the match status between p[:i] and s[:j], i.e. # table[0][0] means the match status of two empty strings, and # table[1][1] means the match status of p[0] and s[0]. Therefore, when # refering to the i-th and the j-th characters of p and s for updating # table[i][j], we use p[i - 1] and s[j - 1]. # Initialize the table with False. The first row is satisfied. table = [[False] * (len(s) + 1) for _ in range(len(p) + 1)] # Update the corner case of matching two empty strings. table[0][0] = True # Update the corner case of when s is an empty string but p is not. # Since each '*' can eliminate the charter before it, the table is # vertically updated by the one before previous. [test_symbol_0] for i in range(2, len(p) + 1): table[i][0] = table[i - 2][0] and p[i - 1] == '*' for i in range(1, len(p) + 1): for j in range(1, len(s) + 1): if p[i - 1] != "*": # Update the table by referring the diagonal element. table[i][j] = table[i - 1][j - 1] and \ (p[i - 1] == s[j - 1] or p[i - 1] == '.') else: # Eliminations (referring to the vertical element) # Either refer to the one before previous or the previous. # I.e. * eliminate the previous or count the previous. # [test_symbol_1] table[i][j] = table[i - 2][j] or table[i - 1][j] # Propagations (referring to the horizontal element) # If p's previous one is equal to the current s, with # helps of *, the status can be propagated from the left. # [test_symbol_2] if p[i - 2] == s[j - 1] or p[i - 2] == '.': table[i][j] |= table[i][j - 1] return table[-1][-1] class TestSolution(unittest.TestCase): def test_none_0(self): s = "" p = "" self.assertTrue(Solution().isMatch(s, p)) def test_none_1(self): s = "" p = "a" self.assertFalse(Solution().isMatch(s, p)) def test_no_symbol_equal(self): s = "abcd" p = "abcd" self.assertTrue(Solution().isMatch(s, p)) def test_no_symbol_not_equal_0(self): s = "abcd" p = "efgh" self.assertFalse(Solution().isMatch(s, p)) def test_no_symbol_not_equal_1(self): s = "ab" p = "abb" self.assertFalse(Solution().isMatch(s, p)) def test_symbol_0(self): s = "" p = "a*" self.assertTrue(Solution().isMatch(s, p)) def test_symbol_1(self): s = "a" p = "ab*" self.assertTrue(Solution().isMatch(s, p)) def test_symbol_2(self): # E.g. # s a b b # p 1 0 0 0 # a 0 1 0 0 # b 0 0 1 0 # * 0 1 1 1 s = "abb" p = "ab*" self.assertTrue(Solution().isMatch(s, p)) if __name__ == "__main__": unittest.main()
C++:
class Solution { public: bool isMatch(string s, string p) { if (p.empty()) return s.empty(); if ('*' == p[1]) // x* matches empty string or at least one character: x* -> xx* // *s is to ensure s is non-empty return (isMatch(s, p.substr(2)) || !s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p)); else return !s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p.substr(1)); } }; class Solution { public: bool isMatch(string s, string p) { /** * f[i][j]: if s[0..i-1] matches p[0..j-1] * if p[j - 1] != '*' * f[i][j] = f[i - 1][j - 1] && s[i - 1] == p[j - 1] * if p[j - 1] == '*', denote p[j - 2] with x * f[i][j] is true iff any of the following is true * 1) "x*" repeats 0 time and matches empty: f[i][j - 2] * 2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && f[i - 1][j] * '.' matches any single character */ int m = s.size(), n = p.size(); vector<vector<bool>> f(m + 1, vector<bool>(n + 1, false)); f[0][0] = true; for (int i = 1; i <= m; i++) f[i][0] = false; // p[0.., j - 3, j - 2, j - 1] matches empty iff p[j - 1] is '*' and p[0..j - 3] matches empty for (int j = 1; j <= n; j++) f[0][j] = j > 1 && '*' == p[j - 1] && f[0][j - 2]; for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (p[j - 1] != '*') f[i][j] = f[i - 1][j - 1] && (s[i - 1] == p[j - 1] || '.' == p[j - 1]); else // p[0] cannot be '*' so no need to check "j > 1" here f[i][j] = f[i][j - 2] || (s[i - 1] == p[j - 2] || '.' == p[j - 2]) && f[i - 1][j]; return f[m][n]; } };
C++:
class Solution { public: bool isMatch(string s, string p) { if (p.empty()) return s.empty(); if ('*' == p[1]) // x* matches empty string or at least one character: x* -> xx* // *s is to ensure s is non-empty return (isMatch(s, p.substr(2)) || !s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p)); else return !s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p.substr(1)); } }; class Solution { public: bool isMatch(string s, string p) { /** * f[i][j]: if s[0..i-1] matches p[0..j-1] * if p[j - 1] != '*' * f[i][j] = f[i - 1][j - 1] && s[i - 1] == p[j - 1] * if p[j - 1] == '*', denote p[j - 2] with x * f[i][j] is true iff any of the following is true * 1) "x*" repeats 0 time and matches empty: f[i][j - 2] * 2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && f[i - 1][j] * '.' matches any single character */ int m = s.size(), n = p.size(); vector<vector<bool>> f(m + 1, vector<bool>(n + 1, false)); f[0][0] = true; for (int i = 1; i <= m; i++) f[i][0] = false; // p[0.., j - 3, j - 2, j - 1] matches empty iff p[j - 1] is '*' and p[0..j - 3] matches empty for (int j = 1; j <= n; j++) f[0][j] = j > 1 && '*' == p[j - 1] && f[0][j - 2]; for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (p[j - 1] != '*') f[i][j] = f[i - 1][j - 1] && (s[i - 1] == p[j - 1] || '.' == p[j - 1]); else // p[0] cannot be '*' so no need to check "j > 1" here f[i][j] = f[i][j - 2] || (s[i - 1] == p[j - 2] || '.' == p[j - 2]) && f[i - 1][j]; return f[m][n]; } };
类似题目:
[LeetCode] 44. Wildcard Matching 外卡匹配