[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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 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++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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 外卡匹配
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构