"""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)