76 lines
2.2 KiB
Python
76 lines
2.2 KiB
Python
"""Regular expression matching supporting '.' and '*' (LeetCode 10).
|
|
|
|
'.' matches any single character; '*' matches zero or more of the
|
|
preceding element. The match must cover the *entire* input string.
|
|
|
|
Two implementations are provided:
|
|
- is_match(s, p): recursive backtracking with memoisation.
|
|
- is_match_dp(s, p): bottom-up DP table.
|
|
|
|
Both agree on all inputs; tests cross-check them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from functools import lru_cache
|
|
|
|
|
|
def _norm(p: str) -> str:
|
|
return p
|
|
|
|
|
|
def is_match(s: str, p: str) -> bool:
|
|
"""Recursive memoised matcher."""
|
|
if not isinstance(s, str) or not isinstance(p, str):
|
|
raise TypeError("s and p must be str")
|
|
|
|
# Precompute lengths.
|
|
n, m = len(s), len(p)
|
|
|
|
from functools import lru_cache
|
|
|
|
@lru_cache(maxsize=None)
|
|
def rec(i: int, j: int) -> bool:
|
|
# i position in s, j position in p
|
|
if j == m:
|
|
return i == n
|
|
# peek next char for '*'
|
|
first = (i < n) and (p[j] == s[i] or p[j] == '.')
|
|
if j + 1 < m and p[j + 1] == '*':
|
|
# zero occurrences OR one+ occurrences
|
|
return rec(i, j + 2) or (first and rec(i + 1, j))
|
|
if first:
|
|
return rec(i + 1, j + 1)
|
|
return False
|
|
|
|
return rec(0, 0)
|
|
|
|
|
|
def is_match_dp(s: str, p: str) -> bool:
|
|
if not isinstance(s, str) or not isinstance(p, str):
|
|
raise TypeError("s and p must be str")
|
|
n, m = len(s), len(p)
|
|
# dp[i][j] = s[:i] matches p[:j]
|
|
dp = [[False] * (m + 1) for _ in range(n + 1)]
|
|
dp[0][0] = True
|
|
# patterns like a*, a*b*, ... match empty string
|
|
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] == '*':
|
|
# zero occurrences
|
|
dp[i][j] = dp[i][j - 2]
|
|
# one+ occurrences
|
|
if (p[j - 2] == s[i - 1] or p[j - 2] == '.') and dp[i - 1][j]:
|
|
dp[i][j] = True
|
|
elif p[j - 1] == '.' or p[j - 1] == s[i - 1]:
|
|
dp[i][j] = dp[i - 1][j - 1]
|
|
else:
|
|
dp[i][j] = False
|
|
return dp[n][m]
|
|
|
|
|
|
# cross-check alias
|
|
def match(s: str, p: str) -> bool:
|
|
return is_match(s, p) |