Back to blog
Sep 08, 2025
15 min read

Regular Expression Matching

Given an input string (s) and a pattern (p), implement regular expression matching with support for '.' and '*'. '.' Matches any single character. '*' Matches zero or more of the preceding element. The matching should cover the entire input string (not partial).

Difficulty: Hard | Acceptance: 29.56% | Paid: No

Topics: String, Dynamic Programming, Recursion

Examples

Input

s = "aa", p = "a"

Output

false

Explanation

”a” does not match the entire string “aa”.

Input

s = "aa", p = "a*"

Output

true

Explanation

’*’ means zero or more of the preceding element, ‘a’. Therefore, by repeating ‘a’ once, it becomes “aa”.

Input

s = "ab", p = ".*"

Output

true

Explanation

”.” means “zero or more () of any character (.)“.

Constraints

- 1 <= s.length <= 20
- 1 <= p.length <= 20
- s contains only lowercase English letters.
- p contains only lowercase English letters, '.', and '*'.
- It is guaranteed for each appearance of the character '*', there will be a previous valid character to match.

Brute Force Recursion

Intuition

A direct recursive approach that checks all possible matches by exploring every possibility of ’*’ and ’.’ characters.

Steps

  • Base cases: if pattern is empty, string must also be empty for a match.
  • If the next character in pattern is ’*’, consider two possibilities: zero occurrence or one/more occurrence.
  • If current characters match or pattern has ’.’, proceed with the rest of string and pattern.
python
def isMatch(s: str, p: str) -> bool:
    if not p:
        return not s
    first_match = bool(s) and p[0] in {s[0], '.'}
    if len(p) &gt;= 2 and p[1] == '*':
        return (isMatch(s, p[2:]) or
                (first_match and isMatch(s[1:], p)))
    else:
        return first_match and isMatch(s[1:], p[1:])

Complexity

  • Time: O((T+P) * 2^(T+P/2)) where T is length of text and P is length of pattern
  • Space: O((T+P) * 2^(T+P/2)) due to recursion stack in worst case
  • Notes: Exponential time due to overlapping subproblems

Top-Down Memoization

Intuition

To optimize the brute-force approach by caching results of subproblems to avoid redundant computation.

Steps

  • Use a memoization table (or dictionary) to store results of (i,j) pairs representing current positions in string and pattern.
  • Before computing result for any (i,j), check if already computed.
  • Otherwise, compute using same recurrence relation but store result in memo table.
python
def isMatch(s: str, p: str) -> bool:
    memo = {}
    def dp(i, j):
        if (i, j) in memo:
            return memo[(i, j)]
        if j == len(p):
            ans = i == len(s)
        else:
            first_match = i &lt; len(s) and p[j] in {s[i], '.'}
            if j+1 &lt; len(p) and p[j+1] == '*':
                ans = (dp(i, j+2) or
                       (first_match and dp(i+1, j)))
            else:
                ans = first_match and dp(i+1, j+1)
        memo[(i, j)] = ans
        return ans
    return dp(0, 0)

Complexity

  • Time: O(T*P) where T is length of text and P is length of pattern
  • Space: O(T*P) for memoization table
  • Notes: Avoids recomputation of overlapping subproblems

Bottom-Up Dynamic Programming

Intuition

Build up solution from smaller subproblems using a 2D table, solving from end to beginning to ensure subproblems are solved before needed.

Steps

  • Create a 2D boolean DP table where dp[i][j] represents whether s[i:] matches p[j:].
  • Initialize base cases: dp[len(s)][len(p)] = true.
  • Fill the table from bottom-right to top-left according to recurrence relation.
python
def isMatch(s: str, p: str) -> bool:
    dp = [[False] * (len(p) + 1) for _ in range(len(s) + 1)]
    dp[len(s)][len(p)] = True
    for i in range(len(s), -1, -1):
        for j in range(len(p) - 1, -1, -1):
            first_match = i &lt; len(s) and p[j] in {s[i], '.'}
            if j+1 &lt; len(p) and p[j+1] == '*':
                dp[i][j] = dp[i][j+2] or (first_match and dp[i+1][j])
            else:
                dp[i][j] = first_match and dp[i+1][j+1]
    return dp[0][0]

Complexity

  • Time: O(T*P) where T is length of text and P is length of pattern
  • Space: O(T*P) for the DP table
  • Notes: Bottom-up approach with iterative filling of DP table