给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符 ‘*’ 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。 p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。 示例 1:
输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。示例 2:
输入: s = "aa" p = "a*" 输出: true 解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。示例 3:
输入: s = "ab" p = ".*" 输出: true 解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。示例 4:
输入: s = "aab" p = "c*a*b" 输出: true 解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。示例 5:
输入: s = "mississippi" p = "mis*is*p*." 输出: false思路:
具体看参考2,然后可结合注释理解。
代码:
// V1.0 class Solution { public boolean isMatch(String s, String p) { // Terminator if(p.isEmpty()) return s.isEmpty(); // 不涉及 "*" ,单元素匹配 boolean match = !s.isEmpty() && ((s.charAt(0) == p.charAt(0)) || p.charAt(0) == '.'); // 涉及 ”*“ ,多元素匹配。* 的作用是 消除前面字符p[j-1],或者复制>=1个字符p[j-1] if(p.length() >= 2 && p.charAt(1) == '*') return isMatch(s, p.substring(2)) || (match && isMatch(s.substring(1), p)); return match && isMatch(s.substring(1), p.substring(1)); } } // V2.0:优化charAt(i),加速 class Solution { public boolean isMatchChar(char[] s, int s1, char[] p, int p1) { if(p1 >= p.length) return s1 >= s.length; boolean match = s1 < s.length && ((s[s1] == p[p1]) || p[p1] == '.'); if(p.length - p1 >= 2 && p[p1 + 1] == '*') return isMatchChar(s, s1, p, p1 + 2) || (match && isMatchChar(s, s1 + 1, p, p1)); return match && isMatchChar(s, s1 + 1, p, p1 + 1); } public boolean isMatch(String s, String p) { char[] ss = s.toCharArray(), pp = p.toCharArray(); return isMatchChar(ss, 0, pp, 0); } }时间复杂度: O ( ( m + n ) ∗ 2 m + 2 n ) O((m+n)∗2^{m+ 2n}) O((m+n)∗2m+2n)
空间复杂度: O ( ( m + n ) ∗ 2 m + 2 n ) O((m+n)∗2^{m+ 2n}) O((m+n)∗2m+2n)
思路: 同上。 代码:
class Solution { int[][] mem; public boolean isMatchChar(char[] s, int s1, char[] p, int p1) { if(p1 >= p.length) return s1 >= s.length; if(mem[s1][p1] != 0) return mem[s1][p1] > 0; boolean match = s1 < s.length && ((s[s1] == p[p1]) || p[p1] == '.'); if(p.length - p1 >= 2 && p[p1 + 1] == '*') { boolean t = isMatchChar(s, s1, p, p1 + 2) || (match && isMatchChar(s, s1 + 1, p, p1)); if(t) mem[s1][p1] = 1; else mem[s1][p1] = -1; return t; } boolean t = match && isMatchChar(s, s1 + 1, p, p1 + 1); if(t) mem[s1][p1] = 1; else mem[s1][p1] = -1; return t; } public boolean isMatch(String s, String p) { this.mem = new int[s.length() + 1][p.length() + 1]; char[] ss = s.toCharArray(), pp = p.toCharArray(); return isMatchChar(ss, 0, pp, 0); } }时间复杂度: O ( m n ) O(mn) O(mn) 空间复杂度: O ( m n ) O(mn) O(mn)
思路:
1、状态定义 dp[i][j]:表示 S 的前 i 个能否被 P 的前 j 个字符匹配。 2、转移方程 dp[i-1][j-1]: 前面字串匹配成功,即 S 的前 i-1 个字符成功匹配上 P 的前 j-1 个字符。 So 1. If p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1]; // dp[i-1][j-1]: 前面字串匹配成功,后面也匹配上,全部匹配成功。 2. If p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1]; // 前面字串匹配成功,现在 j 位置的 "." 可成功匹配 s i 位置的任意一个字符,所以 dp[i][j] = dp[i-1][j-1]. 3. If p.charAt(j) == '*': // 有以下几种可能: --1:if p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] // * 代表 0 个字符,消除p 的 j-1 个字符,这种情况说明 p 的最后两个字符无效,可去除。即dp[i][j]的状态依赖于dp[i][j-2]。 --2:if p.charAt(j-1) == s.charAt(i) or p.charAt(i-1) == '.': dp[i][j] = dp[i-1][j] // * 代表多个字符,这里正向去理解,如果 代表多个,则 s 后面再添一个也不影响。 // 例如: ## 与 ##b* 一旦匹配,则##b 与 ##b* 也匹配成功。 or dp[i][j] = dp[i][j-1] // * 代表 1 个字符,即 * 可以理解为不起作用,所以dp[i][j]=dp[i][j-1] or dp[i][j] = dp[i][j-2] // * 代表 0 个字符,消除p 的 j-1 个字符,这种情况说明 p 的最后两个字符无效,可去除。即dp[i][j]的状态依赖于dp[i][j-2]。 ==>进一步整理如下: 1. p[j−1] 为 普通字符,若s[i-1]==p[j-1],则 dp[i][j] = dp[i-1][j-1],否则匹配失败。 2. p[j−1] 为 '.',则 dp[i][j] = dp[i−1][j−1] 3. p[j−1] 为 '*' : (1)不看,则 dp[i][j] = dp[i][j-2] (2)看, 则 dp[i][j] = dp[i-1][j]代码:
public class Solution { public boolean isMatch(String s, String p) { char[] str = s.toCharArray(), ptr = p.toCharArray(); boolean[][] dp = new boolean[str.length + 1][ptr.length + 1]; dp[0][0] = true; for (int i = 0; i <= str.length; i++) { for (int j = 1; j <= ptr.length; j++) { if(ptr[j - 1] != '*') { if(i > 0 && (str[i - 1] == ptr[j - 1] || ptr[j - 1] == '.')) dp[i][j] = dp[i - 1][j - 1]; }else { //ptr[j - 1] == '*' if(j > 1) dp[i][j] |= dp[i][j - 2]; //不看 if(i > 0 && j > 1 && (str[i - 1] == ptr[j - 2] || ptr[j - 2] == '.'))dp[i][j] |= dp[i - 1][j]; //看 } } } return dp[str.length][ptr.length]; } }时间复杂度: O ( m n ) O(mn) O(mn) 空间复杂度: O ( m n ) O(mn) O(mn)
思路: 仅仅开拓思路,更多见参考3,具体略。 代码: 略。 时间复杂度: 略。 空间复杂度: 略。
1、动态规划 - 从 0 讲解,大白话好理解 2、java递归一步一步的优化到击败100%,以及动态规划,思路清晰 3、正则表达式匹配 4、正则表达式匹配 5、10. Regular Expression Matching