Regular Expression Matching
Match s against p with `.` and `*` — 2D DP over prefix matching.
5 min read
dp string
Problem#
Pattern uses . (any single char) and * (zero or more of the preceding element). Return true iff the entire s matches p.
Examples#
s = "aa", p = "a*"→trues = "ab", p = ".*"→trues = "mississippi", p = "mis*is*p*."→false
Approach#
dp[i][j] = does s[:i] match p[:j]. Base: dp[0][0] = true. For p[j-1] == '*': dp[i][j] = dp[i][j-2] (zero copies) OR (dp[i-1][j] AND s[i-1] matches p[j-2]). Else literal/dot: dp[i][j] = dp[i-1][j-1] && (p[j-1] == '.' || p[j-1] == s[i-1]).
Solution#
class Solution {public: bool isMatch(string s, string p) { int n = s.size(), m = p.size(); vector<vector<bool>> dp(n + 1, vector<bool>(m + 1, false)); dp[0][0] = true; for (int j = 2; j <= m; ++j) if (p[j - 1] == '*') dp[0][j] = dp[0][j - 2]; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { if (p[j - 1] == '*') { dp[i][j] = dp[i][j - 2] || (dp[i - 1][j] && (p[j - 2] == '.' || p[j - 2] == s[i - 1])); } else { dp[i][j] = dp[i - 1][j - 1] && (p[j - 1] == '.' || p[j - 1] == s[i - 1]); } } } return dp[n][m]; }};func isMatch(s string, p string) bool { n, m := len(s), len(p) dp := make([][]bool, n+1) for i := range dp { dp[i] = make([]bool, m+1) } dp[0][0] = true for j := 2; j <= m; j++ { if p[j-1] == '*' { dp[0][j] = dp[0][j-2] } } for i := 1; i <= n; i++ { for j := 1; j <= m; j++ { if p[j-1] == '*' { dp[i][j] = dp[i][j-2] || (dp[i-1][j] && (p[j-2] == '.' || p[j-2] == s[i-1])) } else { dp[i][j] = dp[i-1][j-1] && (p[j-1] == '.' || p[j-1] == s[i-1]) } } } return dp[n][m]}class Solution: def isMatch(self, s: str, p: str) -> bool: n, m = len(s), len(p) dp = [[False] * (m + 1) for _ in range(n + 1)] dp[0][0] = True for j in range(2, m + 1): if p[j - 1] == '*': dp[0][j] = dp[0][j - 2] for i in range(1, n + 1): for j in range(1, m + 1): if p[j - 1] == '*': dp[i][j] = dp[i][j - 2] or ( dp[i - 1][j] and (p[j - 2] == '.' or p[j - 2] == s[i - 1]) ) else: dp[i][j] = dp[i - 1][j - 1] and ( p[j - 1] == '.' or p[j - 1] == s[i - 1] ) return dp[n][m]function isMatch(s, p) { const n = s.length, m = p.length; const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(false)); dp[0][0] = true; for (let j = 2; j <= m; j++) { if (p[j - 1] === '*') dp[0][j] = dp[0][j - 2]; } for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (p[j - 1] === '*') { dp[i][j] = dp[i][j - 2] || (dp[i - 1][j] && (p[j - 2] === '.' || p[j - 2] === s[i - 1])); } else { dp[i][j] = dp[i - 1][j - 1] && (p[j - 1] === '.' || p[j - 1] === s[i - 1]); } } } return dp[n][m];}class Solution { public boolean isMatch(String s, String p) { int n = s.length(), m = p.length(); boolean[][] dp = new boolean[n + 1][m + 1]; dp[0][0] = true; for (int j = 2; j <= m; j++) { if (p.charAt(j - 1) == '*') dp[0][j] = dp[0][j - 2]; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (p.charAt(j - 1) == '*') { dp[i][j] = dp[i][j - 2] || (dp[i - 1][j] && (p.charAt(j - 2) == '.' || p.charAt(j - 2) == s.charAt(i - 1))); } else { dp[i][j] = dp[i - 1][j - 1] && (p.charAt(j - 1) == '.' || p.charAt(j - 1) == s.charAt(i - 1)); } } } return dp[n][m]; }}function isMatch(s: string, p: string): boolean { const n = s.length; const m = p.length; const dp: boolean[][] = Array.from({ length: n + 1 }, () => new Array<boolean>(m + 1).fill(false)); dp[0][0] = true; for (let j = 2; j <= m; j++) { if (p[j - 1] === '*') dp[0][j] = dp[0][j - 2]; } for (let i = 1; i <= n; i++) { for (let j = 1; j <= m; j++) { if (p[j - 1] === '*') { dp[i][j] = dp[i][j - 2] || (dp[i - 1][j] && (p[j - 2] === '.' || p[j - 2] === s[i - 1])); } else { dp[i][j] = dp[i - 1][j - 1] && (p[j - 1] === '.' || p[j - 1] === s[i - 1]); } } } return dp[n][m];}Editorial#
The * case has two branches: “zero copies of the preceding element” (drops two chars from p) or “one more match of that element” (drops one char from s). The first row pre-handles empty s matched against a*b*c* style patterns.
Complexity#
- Time: O(n * m).
- Space: O(n * m).
Concept revision#
Related#