LeetCode 10: Regular Expression Matching (2D DP State Transition)
LeetCode 10Dynamic ProgrammingStringRegexToday we solve LeetCode 10 - Regular Expression Matching.
Source: https://leetcode.com/problems/regular-expression-matching/
English
Problem Summary
Given an input string s and pattern p, return whether the whole string matches the pattern. The pattern supports . (any single char) and * (zero or more of the previous element).
Key Insight
This is a full-string matching problem with local choices caused by *. Dynamic Programming cleanly models this: dp[i][j] means whether s[0..i) matches p[0..j).
Transition Rules
1) Normal char or dot: if current chars match, dp[i][j] = dp[i-1][j-1].
2) Star case p[j-1] == '*':
- Zero occurrence: dp[i][j] |= dp[i][j-2].
- One/more occurrence: if previous pattern char matches s[i-1], then dp[i][j] |= dp[i-1][j].
Initialization
dp[0][0] = true. For empty string against pattern, only forms like a*, a*b* can match, so prefill dp[0][j] when p[j-1] == '*' via dp[0][j-2].
Complexity Analysis
Time: O(mn), Space: O(mn), where m = s.length, n = p.length.
Reference Implementations (Java / Go / C++ / Python / JavaScript)
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int j = 2; j <= n; j++) {
if (p.charAt(j - 1) == '*') {
dp[0][j] = dp[0][j - 2];
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
char pj = p.charAt(j - 1);
if (pj == '*') {
dp[i][j] = dp[i][j - 2];
char prev = p.charAt(j - 2);
if (prev == '.' || prev == s.charAt(i - 1)) {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
} else if (pj == '.' || pj == s.charAt(i - 1)) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
}func isMatch(s string, p string) bool {
m, n := len(s), len(p)
dp := make([][]bool, m+1)
for i := range dp {
dp[i] = make([]bool, n+1)
}
dp[0][0] = true
for j := 2; j <= n; j++ {
if p[j-1] == '*' {
dp[0][j] = dp[0][j-2]
}
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if p[j-1] == '*' {
dp[i][j] = dp[i][j-2]
prev := p[j-2]
if prev == '.' || prev == s[i-1] {
dp[i][j] = dp[i][j] || dp[i-1][j]
}
} else if p[j-1] == '.' || p[j-1] == s[i-1] {
dp[i][j] = dp[i-1][j-1]
}
}
}
return dp[m][n]
}class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
dp[0][0] = true;
for (int j = 2; j <= n; ++j) {
if (p[j - 1] == '*') {
dp[0][j] = dp[0][j - 2];
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p[j - 1] == '*') {
dp[i][j] = dp[i][j - 2];
char prev = p[j - 2];
if (prev == '.' || prev == s[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
} else if (p[j - 1] == '.' || p[j - 1] == s[i - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
};class Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for j in range(2, n + 1):
if p[j - 1] == '*':
dp[0][j] = dp[0][j - 2]
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j - 1] == '*':
dp[i][j] = dp[i][j - 2]
prev = p[j - 2]
if prev == '.' or prev == s[i - 1]:
dp[i][j] = dp[i][j] or dp[i - 1][j]
elif p[j - 1] == '.' or p[j - 1] == s[i - 1]:
dp[i][j] = dp[i - 1][j - 1]
return dp[m][n]var isMatch = function(s, p) {
const m = s.length, n = p.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(false));
dp[0][0] = true;
for (let j = 2; j <= n; j++) {
if (p[j - 1] === '*') dp[0][j] = dp[0][j - 2];
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (p[j - 1] === '*') {
dp[i][j] = dp[i][j - 2];
const prev = p[j - 2];
if (prev === '.' || prev === s[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
} else if (p[j - 1] === '.' || p[j - 1] === s[i - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
};中文
题目概述
给定字符串 s 和模式串 p,判断是否完整匹配。其中 . 表示任意单个字符,* 表示其前一个元素可以出现 0 次或多次。
核心思路
这题难点在于 * 会引入“用不用当前字符”的分支。用二维 DP 最稳:dp[i][j] 表示 s 前 i 个字符与 p 前 j 个字符是否匹配。
状态转移
1)普通字符或 .:当前字符可匹配时,dp[i][j] = dp[i-1][j-1]。
2)当 p[j-1] == '*':
- 把“前一个元素+*”当作出现 0 次:dp[i][j] |= dp[i][j-2]。
- 若前一个元素可匹配 s[i-1],可消耗一个字符继续匹配:dp[i][j] |= dp[i-1][j]。
初始化细节
dp[0][0] = true。空串与模式匹配时,只有像 a*、a*b* 这类结构才可能为真,需要按 dp[0][j] = dp[0][j-2] 预填。
复杂度分析
时间复杂度:O(mn);空间复杂度:O(mn)。
多语言参考实现(Java / Go / C++ / Python / JavaScript)
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
dp[0][0] = true;
for (int j = 2; j <= n; j++) {
if (p.charAt(j - 1) == '*') {
dp[0][j] = dp[0][j - 2];
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
char pj = p.charAt(j - 1);
if (pj == '*') {
dp[i][j] = dp[i][j - 2];
char prev = p.charAt(j - 2);
if (prev == '.' || prev == s.charAt(i - 1)) {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
} else if (pj == '.' || pj == s.charAt(i - 1)) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
}func isMatch(s string, p string) bool {
m, n := len(s), len(p)
dp := make([][]bool, m+1)
for i := range dp {
dp[i] = make([]bool, n+1)
}
dp[0][0] = true
for j := 2; j <= n; j++ {
if p[j-1] == '*' {
dp[0][j] = dp[0][j-2]
}
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if p[j-1] == '*' {
dp[i][j] = dp[i][j-2]
prev := p[j-2]
if prev == '.' || prev == s[i-1] {
dp[i][j] = dp[i][j] || dp[i-1][j]
}
} else if p[j-1] == '.' || p[j-1] == s[i-1] {
dp[i][j] = dp[i-1][j-1]
}
}
}
return dp[m][n]
}class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
dp[0][0] = true;
for (int j = 2; j <= n; ++j) {
if (p[j - 1] == '*') {
dp[0][j] = dp[0][j - 2];
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p[j - 1] == '*') {
dp[i][j] = dp[i][j - 2];
char prev = p[j - 2];
if (prev == '.' || prev == s[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
} else if (p[j - 1] == '.' || p[j - 1] == s[i - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
};class Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for j in range(2, n + 1):
if p[j - 1] == '*':
dp[0][j] = dp[0][j - 2]
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j - 1] == '*':
dp[i][j] = dp[i][j - 2]
prev = p[j - 2]
if prev == '.' or prev == s[i - 1]:
dp[i][j] = dp[i][j] or dp[i - 1][j]
elif p[j - 1] == '.' or p[j - 1] == s[i - 1]:
dp[i][j] = dp[i - 1][j - 1]
return dp[m][n]var isMatch = function(s, p) {
const m = s.length, n = p.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(false));
dp[0][0] = true;
for (let j = 2; j <= n; j++) {
if (p[j - 1] === '*') dp[0][j] = dp[0][j - 2];
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (p[j - 1] === '*') {
dp[i][j] = dp[i][j - 2];
const prev = p[j - 2];
if (prev === '.' || prev === s[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j];
}
} else if (p[j - 1] === '.' || p[j - 1] === s[i - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
};
Comments