Leetcode: 10. Regular Expression Matching
Problem Statement: Given two string '.'
and '*'
(Kleene start). The character '.'
can be matched with any other character.
Simulating all transitions
Let's use an example to explain the idea (kind of how I saw the solution). Suppose that *
). So, for every
With
- Time complexity:
, sincetransition
is andjs
has at most elements on every iteration of the loop. - Space complexity:
. The functiontransition
uses space since it stores a list of values for each position . The variable contains at most values.
from functools import cache def solve(s, p): def is_zero_or_more(j): return p[j + 1 : j + 2] == "*" def expand(j): """Return list of all consecutives zero or more patterns starting on j.""" ans = [j] k = 0 while k < len(ans): if is_zero_or_more(ans[k]): ans.append(ans[k] + 2) k += 1 return ans @cache def transition(j): if j == len(p): return [] if not is_zero_or_more(j): return expand(j + 1) return [j] + expand(j + 2) i, js = 0, [0] if p[1:2] != "*" else [0] + expand(2) while i < len(s) and len(js) > 0: next_js = set() for j in js: if j == len(p): continue assert p[j] != "*" if p[j] == "." or p[j] == s[i]: next_js.update(transition(j)) if len(next_js) > 0: i += 1 js = next_js return len(p) in js and i == len(s) assert solve("aa", "a") == False assert solve("aa", "a*") == True assert solve("ab", ".*") == True assert solve("aabb", "a.*b") == True assert solve("aabb", "a.*bc") == False assert solve("a", ".*") == True assert solve("a", "b") == False assert solve("aaaaaaaaaaaaaaaaaaaa", ".*.*.*.*.*.*.*.*.*.*") == True assert solve("a", ".*a") == True assert solve("b", ".*a") == False assert solve("aaa", ".*aaa") == True assert solve("aaa", "ab*ac*a") == True assert solve("aa", "ab*c*a") == True assert solve("mississippi", "mis*is*p*.") == False assert solve("aaabaaaababcbccbaab", "c*c*.*c*a*..*c*") == True class Solution: def isMatch(self, s: str, p: str) -> bool: return solve(s, p)
Dynamic Programming
We would like to compute a function
from functools import cache def solve(s, p): def match(i, j): return p[j] == "." or s[i] == p[j] def is_zero_or_more(j): return p[j + 1 : j + 2] == "*" @cache def dfs(i, j): if i == len(s): return j == len(p) or (is_zero_or_more(j) and dfs(i, j + 2)) if j == len(p): return False assert p[j] != "*" if is_zero_or_more(j): return dfs(i, j + 2) or ( match(i, j) and (dfs(i + 1, j) or dfs(i + 1, j + 2)) ) elif match(i, j): return dfs(i + 1, j + 1) return False return dfs(0, 0) assert solve("aa", "a") == False assert solve("aa", "a*") == True assert solve("ab", ".*") == True assert solve("aabb", "a.*b") == True assert solve("aabb", "a.*bc") == False assert solve("a", ".*") == True assert solve("a", "b") == False assert solve("aaaaaaaaaaaaaaaaaaaa", ".*.*.*.*.*.*.*.*.*.*") == True assert solve("a", ".*a") == True assert solve("b", ".*a") == False assert solve("aaa", ".*aaa") == True assert solve("aaa", "ab*ac*a") == True assert solve("aa", "ab*c*a") == True assert solve("mississippi", "mis*is*p*.") == False assert solve("aaabaaaababcbccbaab", "c*c*.*c*a*..*c*") == True class Solution: def isMatch(self, s: str, p: str) -> bool: return solve(s, p)
Sometimes, top-down approach doesn't work due stack limit. So, it is a good training to also write a bottom-up solution.
from functools import cache def solve(s, p): def match(i, j): return p[j] == "." or s[i] == p[j] def is_zero_or_more(j): return p[j + 1 : j + 2] == "*" dp = [[False for _ in range(len(p) + 1)] for _ in range(len(s) + 1)] for j in range(len(p), -1, -1): dp[len(s)][j] = j == len(p) or (is_zero_or_more(j) and dp[len(s)][j + 2]) for i in range(len(s) - 1, -1, -1): # reversed(range(len(s))): for j in range(len(p) - 1, -1, -1): # reversed(range(len(p))): if is_zero_or_more(j): dp[i][j] = dp[i][j + 2] or ( match(i, j) and (dp[i + 1][j] or dp[i + 1][j + 2]) ) elif match(i, j): dp[i][j] = dp[i + 1][j + 1] return dp[0][0] assert solve("aa", "a") == False assert solve("aa", "a*") == True assert solve("ab", ".*") == True assert solve("aabb", "a.*b") == True assert solve("aabb", "a.*bc") == False assert solve("a", ".*") == True assert solve("a", "b") == False assert solve("aaaaaaaaaaaaaaaaaaaa", ".*.*.*.*.*.*.*.*.*.*") == True assert solve("a", ".*a") == True assert solve("b", ".*a") == False assert solve("aaa", ".*aaa") == True assert solve("aaa", "ab*ac*a") == True assert solve("aa", "ab*c*a") == True assert solve("mississippi", "mis*is*p*.") == False assert solve("aaabaaaababcbccbaab", "c*c*.*c*a*..*c*") == True class Solution: def isMatch(self, s: str, p: str) -> bool: return solve(s, p)
Both, implementations have
- time complexity:
, and - space complexity:
.