You are given an input string s consisting of lowercase english letters, and a pattern p consisting of lowercase english letters, as well as '.', and '*' characters.
Return true if the pattern matches the entire input string, otherwise return false.
'.' Matches any single character'*' Matches zero or more of the preceding element.Example 1:
Input: s = "aa", p = ".b"
Output: falseExplanation: Regardless of which character we choose for the '.' in the pattern, we cannot match the second character in the input string.
Example 2:
Input: s = "nnn", p = "n*"
Output: trueExplanation: '*' means zero or more of the preceding element, 'n'. We choose 'n' to repeat three times.
Example 3:
Input: s = "xyz", p = ".*z"
Output: trueExplanation: The pattern ".*" means zero or more of any character, so we choose ".." to match "xy" and "z" to match "z".
Constraints:
1 <= s.length <= 201 <= p.length <= 20'*', will be preceded by a valid character or '.'.
You should aim for a solution as good or better than O(m * n) time and O(m * n) space, where m is the length of the string s and n is the length of the string p.
Try to think in terms of recursion and visualize it as a decision tree, where we explore different combinations to match the strings when encountering *. Multiple decisions are made at each step to find a valid matching path. Can you determine the possible decisions at each recursion step?
We recursively iterate through the strings using indices i and j for s and p, respectively. If the characters match or p[j] is '.', we increment both i and j to process the remaining strings. When the next character of string p is '*', we have two choices: skip it (treating it as zero occurrences) or match one or more characters (if s[i] matches p[j]), incrementing i accordingly.
If both indices go out of bounds, we return true; otherwise, we return false. If any recursive path returns true, we immediately return true. This approach is exponential. Can you think of a way to optimize it?
We can use memoization to cache the results of recursive calls and avoid redundant calculations. A hash map or a 2D array can be used to store these results.
We need to check if the string s matches the pattern p, where:
. matches any single character* means "zero or more of the previous character"A recursive approach works because at each step we decide how to match the current part of the pattern with the current part of the string.
The function dfs(i, j) represents:
"Can s[i:] be matched by p[j:]?"
There are two main cases:
*.), and we move both pointers forward.*x* part entirely (use * as zero occurrences)x* part to match one character from the string (if it matches), and stay on the same pattern index to potentially match morem = len(s) and n = len(p).dfs(i, j):i is the current index in sj is the current index in pj reaches the end of the pattern:true only if i also reached the end of the stringmatch is true if i is in bounds and (s[i] == p[j] or p[j] == '.')* (i.e., p[j + 1] == '*'):p[j] and * → dfs(i, j + 2)match is true, consume one character from s and keep pattern at j → dfs(i + 1, j)true if either option works* case):match is true, move both pointers forward → dfs(i + 1, j + 1)falsedfs(0, 0) and return the resultclass Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
def dfs(i, j):
if j == n:
return i == m
match = i < m and (s[i] == p[j] or p[j] == ".")
if (j + 1) < n and p[j + 1] == "*":
return (dfs(i, j + 2) or # don't use *
(match and dfs(i + 1, j))) # use *
if match:
return dfs(i + 1, j + 1)
return False
return dfs(0, 0)Where is the length of the string and is the length of the string .
We need to check if s matches pattern p, where:
. matches any single character* means "zero or more of the previous character"A recursive solution explores all matching possibilities, especially because * can represent many choices.
However, the same (i, j) states get recomputed multiple times, making plain recursion slow.
So we use top-down dynamic programming (memoization).
We define dfs(i, j) as:
"Can the substring s[i:] match the pattern p[j:]?"
Whenever we compute the answer for a state (i, j), we store it in a cache so future calls can reuse it instantly.
m = len(s) and n = len(p).(i, j).dfs(i, j):i is the current index in sj is the current index in pj reaches the end of the pattern:true only if i also reaches the end of the string(i, j) is already in the cache:match is true if i < m and (s[i] == p[j] or p[j] == '.')* (p[j + 1] == '*'):x* part completely → dfs(i, j + 2)match is true, consume one char from s and stay on j → dfs(i + 1, j)* case):match is true, move both pointers forward → dfs(i + 1, j + 1)falsedfs(0, 0) and return the final answerclass Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
cache = {}
def dfs(i, j):
if j == n:
return i == m
if (i, j) in cache:
return cache[(i, j)]
match = i < m and (s[i] == p[j] or p[j] == ".")
if (j + 1) < n and p[j + 1] == "*":
cache[(i, j)] = (dfs(i, j + 2) or
(match and dfs(i + 1, j)))
return cache[(i, j)]
if match:
cache[(i, j)] = dfs(i + 1, j + 1)
return cache[(i, j)]
cache[(i, j)] = False
return False
return dfs(0, 0)Where is the length of the string and is the length of the string .
We want to check whether the string s matches the pattern p, where:
. matches any single character* means "zero or more of the previous character"Instead of recursion, we can solve this using bottom-up dynamic programming by building answers for smaller suffixes of the strings.
We define a DP state that answers:
"Can the substring s[i:] be matched by the pattern p[j:]?"
By filling a table from the end of both strings toward the beginning, we ensure that when we compute a state, all the states it depends on are already known.
dp of size(len(s) + 1) × (len(p) + 1).dp[i][j] represent whether s[i:] matches p[j:].dp[len(s)][len(p)] = true because two empty strings match(i, j):match is true if i < len(s) and (s[i] == p[j] or p[j] == '.')*:x* as zero occurrences → dp[i][j] = dp[i][j + 2]match is true, consume one character from s and stay on the same pattern → dp[i + 1][j]dp[i][j] to true if either option works*:match is true, move both pointers forward:dp[i][j] = dp[i + 1][j + 1]dp[0][0]dp[0][0]class Solution:
def isMatch(self, s: str, p: str) -> bool:
dp = [[False] * (len(p) + 1) for i 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):
match = i < len(s) and (s[i] == p[j] or p[j] == ".")
if (j + 1) < len(p) and p[j + 1] == "*":
dp[i][j] = dp[i][j + 2]
if match:
dp[i][j] = dp[i + 1][j] or dp[i][j]
elif match:
dp[i][j] = dp[i + 1][j + 1]
return dp[0][0]Where is the length of the string and is the length of the string .
We need to determine if string s matches pattern p, where:
. matches any single character* means "zero or more of the previous character"In the bottom-up DP solution, we used a 2D table dp[i][j] meaning:
"Does s[i:] match p[j:]?"
But each row i only depends on:
i + 1)jSo we don't need the full 2D table. We can compress it into:
dp → represents the row for i + 1nextDp → represents the row for iThis keeps the same logic while using much less memory.
dp of size len(p) + 1:s[i+1:] with p[j:]dp[len(p)] = true (empty pattern matches empty string at the very end)i from len(s) down to 0:nextDp for the current row (i)nextDp[len(p)] = (i == len(s))i, iterate j from len(p) - 1 down to 0:match:true if i < len(s) and (s[i] == p[j] or p[j] == '.')*:x* (zero occurrences) → nextDp[j + 2]match, consume one char from s and stay on j → dp[j]nextDp[j]*):match, move both forward → nextDp[j] = dp[j + 1]dp = nextDpdp[0] (matching s[0:] with p[0:])dp[0]class Solution:
def isMatch(self, s: str, p: str) -> bool:
dp = [False] * (len(p) + 1)
dp[len(p)] = True
for i in range(len(s), -1, -1):
nextDp = [False] * (len(p) + 1)
nextDp[len(p)] = (i == len(s))
for j in range(len(p) - 1, -1, -1):
match = i < len(s) and (s[i] == p[j] or p[j] == ".")
if (j + 1) < len(p) and p[j + 1] == "*":
nextDp[j] = nextDp[j + 2]
if match:
nextDp[j] |= dp[j]
elif match:
nextDp[j] = dp[j + 1]
dp = nextDp
return dp[0]Where is the length of the string and is the length of the string .
We want to check if s matches p, where:
. matches any single character* means "zero or more of the previous character"The usual bottom-up DP state is:dp[i][j] = does s[i:] match p[j:]?
A 2D table works, and a 1D array also works if we update carefully.
This "optimal" version goes one step further: it updates the 1D array in-place without creating a second array.
The main challenge with in-place updates is that some transitions need the diagonal value:
dp[i + 1][j + 1] (move both forward)When we compress to 1D, that diagonal value would get overwritten while we move left through j.
To handle this, we keep one extra variable (dp1) that stores the previous diagonal value as we update the row.
So at each (i, j) we can still access:
dp[j] → old value for dp[i + 1][j]dp[j + 2] → current row value for skipping x*dp1 → old diagonal dp[i + 1][j + 1]dp of size len(p) + 1:dp[j] represents whether s[i + 1:] matches p[j:] for the current outer loopdp[len(p)] = true (empty pattern matches empty string at the end)i from len(s) down to 0:dp1 (this will act as the diagonal when j moves left)dp[len(p)] = (i == len(s)) since empty pattern only matches if string is also emptyj from len(p) - 1 down to 0:match:true if i < len(s) and (s[i] == p[j] or p[j] == '.')*:x* → use dp[j + 2]match, consume one char from s and stay on j → use dp[j]dp[j]*):match, move both forward → result is the diagonal dp1dp[j] with the computed resultdp1 to the old dp[j] value before it was overwrittendp[0] represents whether s[0:] matches p[0:]dp[0]class Solution:
def isMatch(self, s: str, p: str) -> bool:
dp = [False] * (len(p) + 1)
dp[len(p)] = True
for i in range(len(s), -1, -1):
dp1 = dp[len(p)]
dp[len(p)] = (i == len(s))
for j in range(len(p) - 1, -1, -1):
match = i < len(s) and (s[i] == p[j] or p[j] == ".")
res = False
if (j + 1) < len(p) and p[j + 1] == "*":
res = dp[j + 2]
if match:
res |= dp[j]
elif match:
res = dp1
dp[j], dp1 = res, dp[j]
return dp[0]Where is the length of the string and is the length of the string .