Before attempting this problem, you should be comfortable with:
Hash Maps - Using dictionaries for efficient key-value lookups and storing mappings
Bijection Concept - Understanding one-to-one correspondence where each key maps to exactly one value and vice versa
String Manipulation - Splitting strings by delimiters and iterating through characters
1. Two Hash Maps
Intuition
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.
Algorithm
Split the string s into an array of words.
If the pattern length does not equal the number of words, return false.
Create two maps: one mapping characters to words, another mapping words to characters.
For each character-word pair, check if existing mappings are consistent with the current pair.
If a conflict is found in either direction, return false. Otherwise, update both mappings.
Return true if all pairs are processed without conflicts.
classSolution:defwordPattern(self, pattern:str, s:str)->bool:
words = s.split(" ")iflen(pattern)!=len(words):returnFalse
charToWord ={}
wordToChar ={}for c, w inzip(pattern, words):if c in charToWord and charToWord[c]!= w:returnFalseif w in wordToChar and wordToChar[w]!= c:returnFalse
charToWord[c]= w
wordToChar[w]= c
returnTrue
publicclassSolution{publicbooleanwordPattern(String pattern,String s){String[] words = s.split(" ");if(pattern.length()!= words.length){returnfalse;}Map<Character,String> charToWord =newHashMap<>();Map<String,Character> wordToChar =newHashMap<>();for(int i =0; i < pattern.length(); i++){char c = pattern.charAt(i);String w = words[i];if(charToWord.containsKey(c)&&!charToWord.get(c).equals(w)){returnfalse;}if(wordToChar.containsKey(w)&& wordToChar.get(w)!= c){returnfalse;}
charToWord.put(c, w);
wordToChar.put(w, c);}returntrue;}}
classSolution{public:boolwordPattern(string pattern, string s){
vector<string> words;
string word;
stringstream ss(s);while(ss >> word){
words.push_back(word);}if(pattern.length()!= words.size()){returnfalse;}
unordered_map<char, string> charToWord;
unordered_map<string,char> wordToChar;for(int i =0; i < pattern.length(); i++){char c = pattern[i];
string& w = words[i];if(charToWord.count(c)&& charToWord[c]!= w){returnfalse;}if(wordToChar.count(w)&& wordToChar[w]!= c){returnfalse;}
charToWord[c]= w;
wordToChar[w]= c;}returntrue;}};
classSolution{/**
* @param {string} pattern
* @param {string} s
* @return {boolean}
*/wordPattern(pattern, s){const words = s.split(' ');if(pattern.length !== words.length){returnfalse;}const charToWord =newMap();const wordToChar =newMap();for(let i =0; i < pattern.length; i++){const c = pattern[i];const w = words[i];if(charToWord.has(c)&& charToWord.get(c)!== w){returnfalse;}if(wordToChar.has(w)&& wordToChar.get(w)!== c){returnfalse;}
charToWord.set(c, w);
wordToChar.set(w, c);}returntrue;}}
publicclassSolution{publicboolWordPattern(string pattern,string s){string[] words = s.Split(' ');if(pattern.Length != words.Length){returnfalse;}Dictionary<char,string> charToWord =newDictionary<char,string>();Dictionary<string,char> wordToChar =newDictionary<string,char>();for(int i =0; i < pattern.Length; i++){char c = pattern[i];string w = words[i];if(charToWord.ContainsKey(c)&& charToWord[c]!= w){returnfalse;}if(wordToChar.ContainsKey(w)&& wordToChar[w]!= c){returnfalse;}
charToWord[c]= w;
wordToChar[w]= c;}returntrue;}}
funcwordPattern(pattern string, s string)bool{
words := strings.Split(s," ")iflen(pattern)!=len(words){returnfalse}
charToWord :=make(map[byte]string)
wordToChar :=make(map[string]byte)for i :=0; i <len(pattern); i++{
c := pattern[i]
w := words[i]if val, ok := charToWord[c]; ok && val != w {returnfalse}if val, ok := wordToChar[w]; ok && val != c {returnfalse}
charToWord[c]= w
wordToChar[w]= c
}returntrue}
class Solution {funwordPattern(pattern: String, s: String): Boolean {val words = s.split(" ")if(pattern.length != words.size){returnfalse}val charToWord = HashMap<Char, String>()val wordToChar = HashMap<String, Char>()for(i in pattern.indices){val c = pattern[i]val w = words[i]if(charToWord.containsKey(c)&& charToWord[c]!= w){returnfalse}if(wordToChar.containsKey(w)&& wordToChar[w]!= c){returnfalse}
charToWord[c]= w
wordToChar[w]= c
}returntrue}}
classSolution{funcwordPattern(_ pattern:String,_ s:String)->Bool{let words = s.split(separator:" ").map {String($0)}if pattern.count != words.count {returnfalse}var charToWord =[Character:String]()var wordToChar =[String:Character]()let patternArray =Array(pattern)for i in0..<patternArray.count {let c = patternArray[i]let w = words[i]iflet mapped = charToWord[c], mapped != w {returnfalse}iflet mapped = wordToChar[w], mapped != c {returnfalse}
charToWord[c]= w
wordToChar[w]= c
}returntrue}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(m)
Where n is the length of the string pattern and m is the length of the string s.
2. Two Hash Maps (Optimal)
Intuition
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.
Algorithm
Split the string into words and verify the lengths match.
Create two maps storing the last seen index for each character and each word.
For each position, check if the stored index for the current character equals the stored index for the current word.
If they differ, the mapping is invalid; return false.
Update both maps with the current index (using i+1 to distinguish from default 0).
classSolution:defwordPattern(self, pattern:str, s:str)->bool:
charToWord ={}
wordToChar ={}
words = s.split()iflen(pattern)!=len(words):returnFalsefor i,(c, word)inenumerate(zip(pattern, words)):if charToWord.get(c,0)!= wordToChar.get(word,0):returnFalse
charToWord[c]= i +1
wordToChar[word]= i +1returnTrue
publicclassSolution{publicbooleanwordPattern(String pattern,String s){Map<Character,Integer> charToWord =newHashMap<>();Map<String,Integer> wordToChar =newHashMap<>();String[] words = s.split(" ");if(words.length != pattern.length())returnfalse;for(int i =0; i < pattern.length(); i++){if(charToWord.containsKey(pattern.charAt(i))&&!words[charToWord.get(pattern.charAt(i))].equals(words[i])){returnfalse;}if(wordToChar.containsKey(words[i])&&
pattern.charAt(wordToChar.get(words[i]))!= pattern.charAt(i)){returnfalse;}
charToWord.put(pattern.charAt(i), i);
wordToChar.put(words[i], i);}returntrue;}}
classSolution{public:boolwordPattern(string pattern, string s){
unordered_map<char,int> charToWord;
unordered_map<string,int> wordToChar;
istringstream in(s);int i =0, n = pattern.size();for(string word; in >> word;++i){if(i == n || charToWord[pattern[i]]!= wordToChar[word]){returnfalse;}
charToWord[pattern[i]]= wordToChar[word]= i +1;}return i == n;}};
classSolution{/**
* @param {string} pattern
* @param {string} s
* @return {boolean}
*/wordPattern(pattern, s){const charToWord =newMap();const wordToChar =newMap();const words = s.split(' ');if(pattern.length !== words.length){returnfalse;}for(let i =0; i < words.length; i++){const c = pattern[i];const word = words[i];if((charToWord.get(c)||0)!==(wordToChar.get(word)||0)){returnfalse;}
charToWord.set(c, i +1);
wordToChar.set(word, i +1);}returntrue;}}
publicclassSolution{publicboolWordPattern(string pattern,string s){var charToWord =newDictionary<char,int>();var wordToChar =newDictionary<string,int>();string[] words = s.Split(' ');if(words.Length != pattern.Length)returnfalse;for(int i =0; i < pattern.Length; i++){
charToWord.TryGetValue(pattern[i],outint charIdx);
wordToChar.TryGetValue(words[i],outint wordIdx);if(charIdx != wordIdx){returnfalse;}
charToWord[pattern[i]]= i +1;
wordToChar[words[i]]= i +1;}returntrue;}}
funcwordPattern(pattern string, s string)bool{
charToWord :=make(map[byte]int)
wordToChar :=make(map[string]int)
words := strings.Split(s," ")iflen(pattern)!=len(words){returnfalse}for i :=0; i <len(words); i++{
c := pattern[i]
word := words[i]if charToWord[c]!= wordToChar[word]{returnfalse}
charToWord[c]= i +1
wordToChar[word]= i +1}returntrue}
class Solution {funwordPattern(pattern: String, s: String): Boolean {val charToWord = HashMap<Char, Int>()val wordToChar = HashMap<String, Int>()val words = s.split(" ")if(words.size != pattern.length)returnfalsefor(i in pattern.indices){val charIdx = charToWord.getOrDefault(pattern[i],0)val wordIdx = wordToChar.getOrDefault(words[i],0)if(charIdx != wordIdx){returnfalse}
charToWord[pattern[i]]= i +1
wordToChar[words[i]]= i +1}returntrue}}
classSolution{funcwordPattern(_ pattern:String,_ s:String)->Bool{var charToWord =[Character:Int]()var wordToChar =[String:Int]()let words = s.split(separator:" ").map {String($0)}let patternArray =Array(pattern)if patternArray.count != words.count {returnfalse}for i in0..<words.count {let c = patternArray[i]let word = words[i]let charIdx = charToWord[c]??0let wordIdx = wordToChar[word]??0if charIdx != wordIdx {returnfalse}
charToWord[c]= i +1
wordToChar[word]= i +1}returntrue}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(m)
Where n is the length of the string pattern and m is the length of the string s.
3. Hash Set
Intuition
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.
Algorithm
Split the string and verify the pattern and word counts match.
Create a map for character to index and a set for used words.
For each character-word pair, if the character was seen before, verify it maps to the same word.
If the character is new, check if the word is already in the used set (which would indicate a duplicate mapping).
If the word is already used by another character, return false. Otherwise, add the mapping and mark the word as used.
classSolution:defwordPattern(self, pattern:str, s:str)->bool:
words = s.split()iflen(pattern)!=len(words):returnFalse
charToWord ={}
store =set()for i,(c, w)inenumerate(zip(pattern, words)):if c in charToWord:if words[charToWord[c]]!= w:returnFalseelse:if w in store:returnFalse
charToWord[c]= i
store.add(w)returnTrue
publicclassSolution{publicbooleanwordPattern(String pattern,String s){String[] words = s.split(" ");if(pattern.length()!= words.length)returnfalse;Map<Character,Integer> charToWord =newHashMap<>();Set<String> store =newHashSet<>();for(int i =0; i < pattern.length(); i++){char c = pattern.charAt(i);if(charToWord.containsKey(c)){if(!words[charToWord.get(c)].equals(words[i])){returnfalse;}}else{if(store.contains(words[i])){returnfalse;}
charToWord.put(c, i);
store.add(words[i]);}}returntrue;}}
classSolution{/**
* @param {string} pattern
* @param {string} s
* @return {boolean}
*/wordPattern(pattern, s){const charToWord =newMap();const wordToChar =newMap();const words = s.split(' ');if(pattern.length !== words.length){returnfalse;}for(let i =0; i < words.length; i++){const c = pattern[i];const word = words[i];if((charToWord.get(c)||0)!==(wordToChar.get(word)||0)){returnfalse;}
charToWord.set(c, i +1);
wordToChar.set(word, i +1);}returntrue;}}
publicclassSolution{publicboolWordPattern(string pattern,string s){string[] words = s.Split(' ');if(pattern.Length != words.Length)returnfalse;Dictionary<char,int> charToWord =newDictionary<char,int>();HashSet<string> store =newHashSet<string>();for(int i =0; i < pattern.Length; i++){char c = pattern[i];if(charToWord.ContainsKey(c)){if(words[charToWord[c]]!= words[i]){returnfalse;}}else{if(store.Contains(words[i])){returnfalse;}
charToWord[c]= i;
store.Add(words[i]);}}returntrue;}}
funcwordPattern(pattern string, s string)bool{
words := strings.Split(s," ")iflen(pattern)!=len(words){returnfalse}
charToWord :=make(map[byte]int)
store :=make(map[string]bool)for i :=0; i <len(pattern); i++{
c := pattern[i]if idx, ok := charToWord[c]; ok {if words[idx]!= words[i]{returnfalse}}else{if store[words[i]]{returnfalse}
charToWord[c]= i
store[words[i]]=true}}returntrue}
class Solution {funwordPattern(pattern: String, s: String): Boolean {val words = s.split(" ")if(pattern.length != words.size)returnfalseval charToWord = HashMap<Char, Int>()val store = HashSet<String>()for(i in pattern.indices){val c = pattern[i]if(charToWord.containsKey(c)){if(words[charToWord[c]!!]!= words[i]){returnfalse}}else{if(store.contains(words[i])){returnfalse}
charToWord[c]= i
store.add(words[i])}}returntrue}}
classSolution{funcwordPattern(_ pattern:String,_ s:String)->Bool{let words = s.split(separator:" ").map {String($0)}let patternArray =Array(pattern)if patternArray.count != words.count {returnfalse}var charToWord =[Character:Int]()var store =Set<String>()for i in0..<patternArray.count {let c = patternArray[i]iflet idx = charToWord[c]{if words[idx]!= words[i]{returnfalse}}else{if store.contains(words[i]){returnfalse}
charToWord[c]= i
store.insert(words[i])}}returntrue}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(m)
Where n is the length of the string pattern and m is the length of the string s.
4. Single Hash Map
Intuition
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.
Algorithm
Split the string and check that pattern and word counts are equal.
Create a single map from characters to their first occurrence index.
For each character-word pair, if the character exists in the map, verify the stored index points to the same word.
If the character is new, iterate through all existing mappings to ensure no other character maps to this word.
If a conflict is found, return false. Otherwise, add the new mapping.
Return true after processing all pairs successfully.
classSolution:defwordPattern(self, pattern:str, s:str)->bool:
words = s.split()iflen(pattern)!=len(words):returnFalse
charToWord ={}for i,(c, w)inenumerate(zip(pattern, words)):if c in charToWord:if words[charToWord[c]]!= w:returnFalseelse:# iterates atmost 26 times (a - z)for k in charToWord:if words[charToWord[k]]== w:returnFalse
charToWord[c]= i
returnTrue
publicclassSolution{publicbooleanwordPattern(String pattern,String s){String[] words = s.split(" ");if(pattern.length()!= words.length){returnfalse;}Map<Character,Integer> charToWord =newHashMap<>();for(int i =0; i < pattern.length(); i++){char c = pattern.charAt(i);String w = words[i];if(charToWord.containsKey(c)){if(!words[charToWord.get(c)].equals(w)){returnfalse;}}else{for(Map.Entry<Character,Integer> entry : charToWord.entrySet()){if(words[entry.getValue()].equals(w)){returnfalse;}}
charToWord.put(c, i);}}returntrue;}}
classSolution{public:boolwordPattern(string pattern, string s){
vector<string> words;
string word;
stringstream ss(s);while(ss >> word){
words.push_back(word);}if(pattern.size()!= words.size()){returnfalse;}
unordered_map<char,int> charToWord;for(int i =0; i < pattern.size();++i){char c = pattern[i];const string& w = words[i];if(charToWord.count(c)){if(words[charToWord[c]]!= w){returnfalse;}}else{for(constauto&[key, val]: charToWord){if(words[val]== w){returnfalse;}}
charToWord[c]= i;}}returntrue;}};
classSolution{/**
* @param {string} pattern
* @param {string} s
* @return {boolean}
*/wordPattern(pattern, s){const words = s.split(' ');if(pattern.length !== words.length){returnfalse;}const charToWord =newMap();for(let i =0; i < pattern.length; i++){const c = pattern[i];const w = words[i];if(charToWord.has(c)){if(words[charToWord.get(c)]!== w){returnfalse;}}else{for(const[key, index]of charToWord.entries()){if(words[index]=== w){returnfalse;}}
charToWord.set(c, i);}}returntrue;}}
publicclassSolution{publicboolWordPattern(string pattern,string s){string[] words = s.Split(' ');if(pattern.Length != words.Length){returnfalse;}Dictionary<char,int> charToWord =newDictionary<char,int>();for(int i =0; i < pattern.Length; i++){char c = pattern[i];string w = words[i];if(charToWord.ContainsKey(c)){if(words[charToWord[c]]!= w){returnfalse;}}else{foreach(var entry in charToWord){if(words[entry.Value]== w){returnfalse;}}
charToWord[c]= i;}}returntrue;}}
funcwordPattern(pattern string, s string)bool{
words := strings.Split(s," ")iflen(pattern)!=len(words){returnfalse}
charToWord :=make(map[byte]int)for i :=0; i <len(pattern); i++{
c := pattern[i]
w := words[i]if idx, ok := charToWord[c]; ok {if words[idx]!= w {returnfalse}}else{for_, val :=range charToWord {if words[val]== w {returnfalse}}
charToWord[c]= i
}}returntrue}
class Solution {funwordPattern(pattern: String, s: String): Boolean {val words = s.split(" ")if(pattern.length != words.size){returnfalse}val charToWord = HashMap<Char, Int>()for(i in pattern.indices){val c = pattern[i]val w = words[i]if(charToWord.containsKey(c)){if(words[charToWord[c]!!]!= w){returnfalse}}else{for((_, idx)in charToWord){if(words[idx]== w){returnfalse}}
charToWord[c]= i
}}returntrue}}
classSolution{funcwordPattern(_ pattern:String,_ s:String)->Bool{let words = s.split(separator:" ").map {String($0)}let patternArray =Array(pattern)if patternArray.count != words.count {returnfalse}var charToWord =[Character:Int]()for i in0..<patternArray.count {let c = patternArray[i]let w = words[i]iflet idx = charToWord[c]{if words[idx]!= w {returnfalse}}else{for(_, idx)in charToWord {if words[idx]== w {returnfalse}}
charToWord[c]= i
}}returntrue}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(m)
Where n is the length of the string pattern and m is the length of the string s.
Common Pitfalls
Only Checking One Direction of the Mapping
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 -> wordif c in charToWord and charToWord[c]!= w:returnFalse
charToWord[c]= w # Missing reverse check!
Forgetting to Check Length Mismatch Early
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 loopiflen(pattern)!=len(words):returnFalse