The problem
Implement matching for a pattern p against a string s, where:
'.'matches any single character, and'*'means zero or more copies of the character just before it.
The match must cover the whole string. s contains lowercase letters; p contains lowercase letters, '.' and '*', and every '*' follows a valid character.
Input: s = "aab", p = "c*a*b"
Output: Truec* matches zero cs, a* matches "aa", and b matches b.
How to think about it
Start with brute force. Recursively match character by character, and at each x* try “use it zero times” and “use it once more”. Without memoisation the branching blows up on patterns like a*a*a*a*b, giving exponential time.
The key insight: the state is two prefix lengths. Let dp[i][j] mean ”s[:i] matches p[:j]”. Look at the last pattern character p[j-1]:
- A letter or
'.': it must matchs[i-1], and the rest must match:dp[i][j] = dp[i-1][j-1]. - A
'*': the pairx*(wherex = p[j-2]) has two options. Zero copies: drop the pair,dp[i][j-2]. One more copy: ifxmatchess[i-1], consume that character and stay on the same pattern position,dp[i-1][j]. Either option is enough.
The empty-string row is not all False. Patterns like a* or a*b* match the empty string. So dp[0][j] = dp[0][j-2] whenever p[j-1] is '*'.
Which pattern is this? 2-D DP over two sequences (string vs pattern). The tell is “does this pattern match the whole string” with a repetition operator; the star’s “stay on j” step is what makes it unbounded.
Approach
- Make a
(len(s) + 1) × (len(p) + 1)table ofFalse, withdp[0][0] = True. - Fill row 0: for each
'*'atp[j-1], setdp[0][j] = dp[0][j-2]. - For each
i ≥ 1,j ≥ 1:- If
p[j-1]is'*': start withdp[i][j-2](zero copies). Ifp[j-2]is'.'or equalss[i-1], also allowdp[i-1][j]. - Else if
p[j-1]is'.'or equalss[i-1]:dp[i][j] = dp[i-1][j-1].
- If
- Return
dp[-1][-1].
Solution
def is_match(s, p):
dp = [[False]*(len(p)+1) for _ in range(len(s)+1)] # dp[i][j]: s[:i] matches p[:j]
dp[0][0] = True
for j in range(1, len(p)+1):
if p[j-1] == '*': dp[0][j] = dp[0][j-2] # x* can match the empty string
for i in range(1, len(s)+1):
for j in range(1, len(p)+1):
if p[j-1] == '*':
dp[i][j] = dp[i][j-2] # zero copies of x
if p[j-2] == '.' or p[j-2] == s[i-1]: dp[i][j] = dp[i][j] or dp[i-1][j] # one more x
elif p[j-1] == '.' or p[j-1] == s[i-1]:
dp[i][j] = dp[i-1][j-1] # single-character match
return dp[-1][-1]
# is_match("aa", "a*") -> True ; is_match("mississippi", "mis*is*p*.") -> FalseDry run
The table for s = "aab" (rows) and p = "c*a*b" (columns), 1 = True:
| "" | c | * | a | * | b | |
|---|---|---|---|---|---|---|
| "" | 1 | 0 | 1 | 0 | 1 | 0 |
| a | 0 | 0 | 0 | 1 | 1 | 0 |
| a | 0 | 0 | 0 | 0 | 1 | 0 |
| b | 0 | 0 | 0 | 0 | 0 | 1 |
Row 0: c* and c*a* both match the empty string. In row 2, the * column is 1 because a matches and the cell above ("a" vs c*a*) is 1: “one more a”. The bottom-right cell is 1, so the answer is True.
Complexity
- Time: O(m · n). Each cell is constant work.
- Space: O(m · n) for the table; two rows suffice if you roll it.
Edge cases and pitfalls
- Empty string with a star pattern:
is_match("", "a*b*")isTrue; forgetting the row-0 setup gets this wrong. ".*"matches everything, including the empty string.- The star applies to the previous character, not to everything before it.
"ab*"meansafollowed by any number ofbs. - Partial matches don’t count.
is_match("aa", "a")isFalse.
Related problems
- Edit Distance (LeetCode 72): the same prefix-pair table, with costs instead of booleans.
- Interleaving String (LeetCode 97): a boolean two-string DP with similar transitions.
- Word Break (LeetCode 139): a simpler boolean “can this string be matched” DP over prefixes.
- 1.Unique Paths (LeetCode 62)
- 2.Unique Paths II (LeetCode 63)
- 3.Minimum Path Sum (LeetCode 64)
- 4.Longest Common Subsequence (LeetCode 1143)
- 5.Edit Distance (LeetCode 72)
- 6.Maximal Square (LeetCode 221)
- 7.Longest Palindromic Substring (LeetCode 5)
- 8.Longest Palindromic Subsequence (LeetCode 516)
- 9.Interleaving String (LeetCode 97)
- 10.Distinct Subsequences (LeetCode 115)
- 11.Dungeon Game (LeetCode 174)
- 12.Regular Expression Matching (LeetCode 10)