Problem
Given a pattern
and a string s
, return true
if s
matches the pattern
.
A string s
matches a pattern
if there is some bijective mapping of single characters to non-empty strings such that if each character in pattern
is replaced by the string it maps to, then the resulting string is s
. A bijective mapping means that no two characters map to the same string, and no character maps to two different strings.
Examples
Example 1:
Input: pattern = "abab", str = "redblueredblue"
Output: true
Example 2:
Input: pattern = pattern = "aaaa", str = "asdasdasdasd"
Output: true
Example 3:
Input: pattern = "aabb", str = "xyzabcxzyabc"
Output: false
Notes
You may assume both pattern and str contains only lowercase letters.
This is a follow up of Word Pattern 1 Problem.
Solution
Method 1 - Backtracking with 2 Hashmaps 🏆
We can use 2 hashmaps:
- Use a HashMap to map characters in the pattern to substrings in
s
. - Use another HashMap to ensure the mapping is bijective.
- Use recursion to explore mappings until the entire pattern and string have been matched or a mismatch is detected.
We will have following cases in backtracking:
- If both the pattern and string are fully matched, return
true
. - If either the pattern or the string is fully matched but the other is not, return
false
. - Ensure proper backtracking by removing the mapping when a mismatch occurs.
For edge case, don’t forget below part during DFS.
map.remove(patternChar); // @note: need to remove, since this attempt is failed
inverseMap.remove(tentativeMatchString);
Code
Java
public class Solution {
public boolean wordPatternMatch(String pattern, String s) {
Map<Character, String> map = new HashMap<>();
Map<String, Character> inverseMap = new HashMap<>();
return dfs(pattern, s, 0, 0, map, inverseMap);
}
private boolean dfs(String pat, String str, int patIdx, int strIdx, Map<Character, String> map, Map<String, Character> inverseMap) {
if (patIdx == pat.length() && strIdx == str.length()) {
return true;
}
if (patIdx == pat.length() || strIdx == str.length()) {
return false;
}
char patternChar = pat.charAt(patIdx);
for (int i = strIdx; i < str.length(); ++i) {
String tentativeMatchString = str.substring(strIdx, i + 1);
if (map.containsKey(patternChar)) {
if (map.get(patternChar).equals(tentativeMatchString) && dfs(pat, str, patIdx + 1, i + 1, map, inverseMap)) {
return true;
}
} else if (!inverseMap.containsKey(tentativeMatchString)) {
map.put(patternChar, tentativeMatchString);
inverseMap.put(tentativeMatchString, patternChar);
if (dfs(pat, str, patIdx + 1, i + 1, map, inverseMap)) {
return true;
}
map.remove(patternChar);
inverseMap.remove(tentativeMatchString);
}
}
return false;
}
}
Python
class Solution:
def wordPatternMatch(self, pattern: str, s: str) -> bool:
def dfs(pat: str, s: str, patIdx: int, strIdx: int, map: Dict[chr, str], inverseMap: Dict[str, chr]) -> bool:
if patIdx == len(pat) and strIdx == len(s):
return True
if patIdx == len(pat) or strIdx == len(s):
return False
patternChar = pat[patIdx]
for i in range(strIdx, len(s)):
tentativeMatchString = s[strIdx:i + 1]
if patternChar in map:
if map[patternChar] == tentativeMatchString and dfs(pat, s, patIdx + 1, i + 1, map, inverseMap):
return True
elif tentativeMatchString not in inverseMap:
map[patternChar] = tentativeMatchString
inverseMap[tentativeMatchString] = patternChar
if dfs(pat, s, patIdx + 1, i + 1, map, inverseMap):
return True
del map[patternChar]
del inverseMap[tentativeMatchString]
return False
return dfs(pattern, s, 0, 0, {}, {})
Complexity
- ⏰ Time complexity:
O(n^m)
wheren
is the length of the string andm
is the length of the pattern. - 🧺 Space complexity:
O(m + n)
for storing the mappings and recursive call stack.