Files
troupe-standup/wizard-work/beam/regex_match.py
T

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)