A valid pattern match requires a bijection (one-to-one correspondence) between pattern characters and words. Each character must map to exactly one word, and each word must map to exactly one character. Using two hash maps allows us to verify both directions of this mapping simultaneously as we iterate through the pattern and words.
class Solution:
def wordPattern(self, pattern: str, s: str) -> bool:
words = s.split(" ")
if len(pattern) != len(words):
return False
charToWord = {}
wordToChar = {}
for c, w in zip(pattern, words):
if c in charToWord and charToWord[c] != w:
return False
if w in wordToChar and wordToChar[w] != c:
return False
charToWord[c] = w
wordToChar[w] = c
return TrueWhere is the length of the string and is the length of the string .
Instead of storing the actual mapping, we store the index where each character or word was last seen. If a character and word form a valid pair, they should always have been last seen at the same index. This elegant approach uses the index as a signature that both the character and word must share to be valid mappings.
class Solution:
def wordPattern(self, pattern: str, s: str) -> bool:
charToWord = {}
wordToChar = {}
words = s.split()
if len(pattern) != len(words):
return False
for i, (c, word) in enumerate(zip(pattern, words)):
if charToWord.get(c, 0) != wordToChar.get(word, 0):
return False
charToWord[c] = i + 1
wordToChar[word] = i + 1
return TrueWhere is the length of the string and is the length of the string .
We can use a hash map to track character to word mappings and a hash set to track which words have already been assigned to some character. When we encounter a new character, we check if its corresponding word is already used by another character. This ensures the bijection property using less memory than two full maps.
class Solution:
def wordPattern(self, pattern: str, s: str) -> bool:
words = s.split()
if len(pattern) != len(words):
return False
charToWord = {}
store = set()
for i, (c, w) in enumerate(zip(pattern, words)):
if c in charToWord:
if words[charToWord[c]] != w:
return False
else:
if w in store:
return False
charToWord[c] = i
store.add(w)
return TrueWhere is the length of the string and is the length of the string .
Using only one hash map from character to index, we can still verify the bijection by iterating through existing mappings when encountering a new character. Since there are at most 26 lowercase letters, this iteration is bounded by a constant. We check if any existing character already maps to the current word, ensuring no two characters share the same word.
class Solution:
def wordPattern(self, pattern: str, s: str) -> bool:
words = s.split()
if len(pattern) != len(words):
return False
charToWord = {}
for i, (c, w) in enumerate(zip(pattern, words)):
if c in charToWord:
if words[charToWord[c]] != w:
return False
else:
# iterates atmost 26 times (a - z)
for k in charToWord:
if words[charToWord[k]] == w:
return False
charToWord[c] = i
return TrueWhere is the length of the string and is the length of the string .
A common mistake is using only one hash map to check if each pattern character maps to the same word, but forgetting to verify the reverse. This fails cases like pattern = "abba" and s = "dog dog dog dog" where a -> dog and b -> dog would both be stored, incorrectly returning true.
# Wrong: Only checks character -> word
if c in charToWord and charToWord[c] != w:
return False
charToWord[c] = w # Missing reverse check!Failing to compare the pattern length with the word count before iterating leads to index errors or incorrect results. For example, pattern = "ab" with s = "dog" has a mismatch that should immediately return false.
words = s.split()
# Wrong: Missing this check before the loop
if len(pattern) != len(words):
return False