Introduction
LeetCode 44: Wildcard Matching is a problem where you're asked to determine if a given string matches a pattern that includes wildcard characters. The wildcard characters are:
* which matches any sequence of characters (including an empty sequence).
? which matches exactly one character.
This problem requires implementing a dynamic programming solution, as it's similar to regular expression matching, but with simpler wildcard rules.
Problem Statement
Given a string s and a pattern p, return true if s matches the pattern, otherwise return false. The pattern may contain the following wildcard characters:
? matches any single character.
* matches any sequence of characters (including an empty sequence).
Examples
go
Input: s = "aa", p = "a*"
Output: true
go
Input: s = "mississippi", p = "mis*is*p*."
Output: false
go
Input: s = "ab", p = "?*"
Output: true
Approach: Dynamic Programming
We can solve this problem using a 2D dynamic programming (DP) table where each entry dp[i][j] represents whether the first i characters of string s match the first j characters of pattern p.
Steps:
- Initialize a DP Table:
- Let
dp[i][j] be true if the first i characters of s match the first j characters of p. Otherwise, dp[i][j] is false.
- Set
dp[0][0] = true because an empty string matches an empty pattern.
- Handle
* in the Pattern:
- If the current character in the pattern is
*, it can either match an empty sequence or match one or more characters from the string s.
- This can be represented as:
dp[i][j] = dp[i][j-1] || dp[i-1][j], where dp[i][j-1] indicates the * matches an empty sequence and dp[i-1][j] indicates the * matches one or more characters.
- Handle
? in the Pattern:
- If the current character in the pattern is
?, it matches exactly one character from s. So, dp[i][j] = dp[i-1][j-1].
- Handle Exact Character Matches:
- If the current characters in both the string and the pattern are the same (or the pattern character is
?), then dp[i][j] = dp[i-1][j-1].
- Final Result:
- After filling the DP table,
dp[m][n] will contain the result, where m is the length of string s and n is the length of pattern p.
Go Implementation
go
func isMatch(s string, p string) bool {
m, n := len(s), len(p)
// DP table where dp[i][j] represents whether s[0..i-1] matches p[0..j-1]
dp := make([][]bool, m+1)
for i := range dp {
dp[i] = make([]bool, n+1)
}
// Initialize the DP table
dp[0][0] = true
// Handle the case where the pattern starts with '*' (matches empty string)
for j := 1; j <= n; j++ {
if p[j-1] == '*' {
dp[0][j] = dp[0][j-1]
}
}
// Fill the DP table
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if p[j-1] == '*' {
dp[i][j] = dp[i][j-1] || dp[i-1][j]
} else if p[j-1] == '?' || s[i-1] == p[j-1] {
dp[i][j] = dp[i-1][j-1]
}
}
}
return dp[m][n]
}
Example Walkthrough
Example 1:
go
Input: s = "aa", p = "a*"
- Initialization:
dp[0][0] = true
- For
j = 1, dp[0][1] = false
- For
j = 2, dp[0][2] = true (as * can match empty sequence).
- Filling the DP Table:
- For
i = 1, dp[1][1] = true (as a matches a).
- For
i = 1, dp[1][2] = true (as * can match the empty sequence).
- Output:
dp[2][2] = true (i.e., "aa" matches "a*").
Example 2:
go
Input: s = "mississippi", p = "mis*is*p*."
- Initialization:
- Start with
dp[0][0] = true.
- Handle
* in the pattern, filling the DP table based on the wildcard matching logic.
- Filling the DP Table:
- The matching fails when trying to match characters
p[8] and p[9] against the string.
- Output:
dp[11][11] = false (i.e., "mississippi" does not match "misisp*.").
Time and Space Complexity
MetricComplexityTime ComplexityO(m * n)Space ComplexityO(m * n)
- Time Complexity: The algorithm iterates over a 2D DP table of size
m * n, where m is the length of string s and n is the length of pattern p. Hence, the time complexity is O(m * n).
- Space Complexity: We use a 2D DP table of size
(m+1) x (n+1), so the space complexity is O(m * n).
Edge Cases
- Empty String or Pattern: If either the string
s or the pattern p is empty, handle accordingly.
- An empty string matches an empty pattern or a pattern consisting solely of
*.
- An empty pattern matches an empty string.
- Pattern with Only Wildcards: A pattern consisting of only
* can match any string, including an empty string.
- Pattern with Only Exact Matches: If the pattern consists of characters without wildcards, it must match the string exactly.
Key Takeaways
- Dynamic programming is an optimal solution for problems involving pattern matching with wildcards.
- The 2D DP table helps break the problem into subproblems, solving the problem for each prefix of
s and p.
- Edge cases like empty strings, patterns with only
*, or exact matches should be carefully handled.