|
| 1 | +# Word Pattern |
| 2 | + |
| 3 | +Given a `pattern` and a string `s`, find if s follows the same pattern. |
| 4 | + |
| 5 | +Here follow means a full match, such that there is a bijection between a letter in `pattern` and a non-empty word in `s`. |
| 6 | +Specifically: |
| 7 | + |
| 8 | +- Each letter in `pattern` maps to exactly one unique word in `s`. |
| 9 | +- Each unique word in `s` maps to exactly one letter in `pattern`. |
| 10 | +- No two letters map to the same word, and no two words map to the same letter. |
| 11 | + |
| 12 | +## Examples |
| 13 | + |
| 14 | +Example 1: |
| 15 | + |
| 16 | +```text |
| 17 | +Input: pattern = "abba", s = "dog cat cat dog" |
| 18 | +Output: true |
| 19 | +Explanation: |
| 20 | +The bijection can be established as: |
| 21 | +
|
| 22 | +'a' maps to "dog". |
| 23 | +'b' maps to "cat". |
| 24 | +``` |
| 25 | + |
| 26 | +Example 2: |
| 27 | + |
| 28 | +```text |
| 29 | +Input: pattern = "abba", s = "dog cat cat fish" |
| 30 | +
|
| 31 | +Output: false |
| 32 | +``` |
| 33 | + |
| 34 | +Example 3: |
| 35 | + |
| 36 | +```text |
| 37 | +Input: pattern = "aaaa", s = "dog cat cat dog" |
| 38 | +
|
| 39 | +Output: false |
| 40 | +``` |
| 41 | + |
| 42 | +## Constraints |
| 43 | + |
| 44 | +- 1 <= `pattern.lengt`h <= 300 |
| 45 | +- `pattern` contains only lower-case English letters. |
| 46 | +- 1 <= `s.length` <= 3000 |
| 47 | +- `s` contains only lowercase English letters and spaces ' '. |
| 48 | +- `s` does not contain any leading or trailing spaces. |
| 49 | +- All the words in `s` are separated by a single space. |
| 50 | + |
| 51 | +## Topics |
| 52 | + |
| 53 | +- Hash Table |
| 54 | +- String |
| 55 | + |
| 56 | +## Solution(s) |
| 57 | + |
| 58 | +1. [Using two hash maps](#using-two-hash-maps) |
| 59 | +2. [Using One hash map](#using-one-hash-map) |
| 60 | + |
| 61 | +### Using two hash maps |
| 62 | + |
| 63 | +The key insight is recognizing that we need to maintain a bidirectional mapping between pattern characters and words. |
| 64 | +Think of it like a translation dictionary that works both ways - if we know that 'a' translates to "dog", then "dog" |
| 65 | +must always translate back to 'a'. |
| 66 | + |
| 67 | +Why do we need two hash tables instead of just one? Consider this scenario: |
| 68 | + |
| 69 | +- If we only track that 'a' -> "dog" and 'b' -> "dog", we wouldn't catch the error that two different letters map to the |
| 70 | + same word. |
| 71 | +- Similarly, if pattern is "aa" and string is "dog cat", using only one mapping wouldn't detect that the same letter 'a' |
| 72 | + is trying to map to two different words. |
| 73 | + |
| 74 | +The bidirectional check ensures both conditions of the bijection are satisfied: |
| 75 | + |
| 76 | +- Forward mapping (d1): Ensures each pattern character consistently maps to the same word |
| 77 | +- Reverse mapping (d2): Ensures each word consistently maps to the same pattern character |
| 78 | + |
| 79 | +As we iterate through the pattern and words simultaneously using zip, we check: |
| 80 | + |
| 81 | +- If we've seen this pattern character before (a in d1), does it still map to the same word? |
| 82 | +- If we've seen this word before (b in d2), does it still map to the same pattern character? |
| 83 | + |
| 84 | +If either check fails, we know the bijection is broken. If we successfully process all pairs without conflicts, the |
| 85 | +pattern matches. |
| 86 | + |
| 87 | +The initial length check (len(pattern) != len(ws)) is a quick optimization - if the pattern has a different number of |
| 88 | +characters than we have words, there's no possible valid mapping. |
| 89 | + |
| 90 | +### Time and Space Complexity |
| 91 | + |
| 92 | +#### Time Complexity: O(m + n), where `m` is the length of the pattern string and `n` is the length of string `s` |
| 93 | + |
| 94 | +- Splitting string `s` by spaces takes `O(n)` time as it needs to traverse the entire string. |
| 95 | +- The zip operation combined with the loop iterates through `min(pattern length, words length)` elements, which is at |
| 96 | + most `O(m)`iterations after the length check. |
| 97 | +- Inside each iteration, dictionary lookups and insertions (in operator and assignment) take `O(1)` average time. |
| 98 | + |
| 99 | +Therefore, the overall time complexity is `O(n)` for splitting + `O(m)` for the loop = `O(m + n)`. |
| 100 | + |
| 101 | +#### Space Complexity: O(m + n), where `m` is the length of the pattern string and `n` is the length of string `s` |
| 102 | + |
| 103 | +- The `words` list stores all words from string `s`, which takes `O(n)` space in the worst case (when `s` contains no |
| 104 | + spaces, the entire string is one word). |
| 105 | +- Dictionary `pattern_to_word` stores at most `m` key-value pairs (pattern characters to words), requiring `O(m)` space |
| 106 | + for keys plus the space for word values. |
| 107 | +- Dictionary `word_to_pattern` stores at most `m` key-value pairs (words to pattern characters), requiring space for word |
| 108 | + keys plus `O(m)` space for character values. |
| 109 | +- The total space used by both dictionaries is proportional to the number of unique pattern characters and unique words, |
| 110 | + bounded by `O(m + n)`. |
| 111 | + |
| 112 | +Therefore, the overall space complexity is `O(m + n)`. |
| 113 | + |
| 114 | +### Using One Hash Map |
| 115 | + |
| 116 | +The algorithm checks whether a given string of words follows the same pattern as a sequence of characters, ensuring a |
| 117 | +one-to-one correspondence (bijection) between pattern characters and words. It begins by splitting the string s into |
| 118 | +individual words and verifying that the number of characters in the pattern matches the number of words; if not, it |
| 119 | +immediately returns False. Using a hash map, the algorithm records the first index at which each pattern character and |
| 120 | +word appear by creating unique keys. As it iterates through the words, it checks that the indices associated with a |
| 121 | +character and its corresponding word remain consistent, ensuring that each character maps to exactly one word and each |
| 122 | +word maps to exactly one character. If any mismatch occurs in this mapping, return False; otherwise, return True, |
| 123 | +confirming that the string follows the specified pattern. |
| 124 | + |
| 125 | +The steps of the algorithm are as follows: |
| 126 | + |
| 127 | +1. Create an empty dictionary map_index to store the mapping indices for pattern characters and words. |
| 128 | +2. Use s.split() to divide the string into a list of words. |
| 129 | +3. If the number of characters in the pattern does not equal the number of words, return False immediately. |
| 130 | +4. Iterate through each character–word pair simultaneously using their indices. |
| 131 | + - For each character c and word w, form two distinct keys: |
| 132 | + - `char_key = 'char_{}'.format(c)` |
| 133 | + - `char_word = 'word_{}'.format(w)` |
| 134 | + - Assign or compare mapping indices: |
| 135 | + - If a key does not exist in map_index, assign its value as the current index i. |
| 136 | + - After the assignment, compare the stored indices for both keys. |
| 137 | + - If the indices do not match, return False (indicating inconsistent mapping). |
| 138 | +5. After processing all pairs without inconsistencies, return True, confirming that the pattern matches the word sequence |
| 139 | + correctly. |
| 140 | + |
| 141 | +#### Time and Space Complexity |
| 142 | + |
| 143 | +##### Time Complexity |
| 144 | + |
| 145 | +The time complexity of the above solution is `O(n)` due to splitting the string and iterating through all characters and |
| 146 | +words once. Each lookup and insertion in the hash map takes `O(1)` on average, making the total linear in input size. |
| 147 | + |
| 148 | +##### Space Complexity |
| 149 | + |
| 150 | +The space complexity of the above solution is `O(n)` because it stores all unique pattern–word mappings and the list of |
| 151 | +words. The hash map and word list grow proportionally to the number of words in the input. |
0 commit comments