Keyboard Row: Hash Map Row Lookup
LeetCode 500, Keyboard Row, is an easy problem that asks you to filter words based on a physical keyboard layout. Given an array of strings, you need to return the words that can be typed using letters from only one row of an American QWERTY keyboard. The three rows are qwertyuiop, asdfghjkl, and zxcvbnm. It looks simple on the surface, but how you organize the row lookup determines whether your code is clean or messy.
The problem
You are given an array of strings words. Return the words that can be typed using letters of the alphabet on only one row of the American QWERTY keyboard.
For example, given words = ["Hello", "Alaska", "Dad", "Peace"], the answer is ["Alaska", "Dad"]. "Alaska" uses only letters from the second row, and "Dad" uses only letters from the third row. "Hello" and "Peace" mix letters from multiple rows.
The brute force
The most direct approach is to define each row as a string or set, then for every word, check each character against all three rows to determine which row it belongs to. You could loop through the rows, check membership, and track whether all characters fall on the same row. This works, but you end up writing nested conditions and repeated row-checking logic. If you are not careful, the code becomes a tangled mess of index tracking and boolean flags.
The key insight
Instead of checking characters against three separate row sets, you can build a hash map that maps every letter directly to its row number. Each letter in qwertyuiop maps to 1, each letter in asdfghjkl maps to 2, and each letter in zxcvbnm maps to 3. Now, to check a word, you look up the row of the first letter, then verify that every other letter maps to the same row number.
Building a hash map up front turns a three-way membership check into a single dictionary lookup per character. This is a common pattern whenever you need to categorize items into groups.
This approach separates the "what row is this letter on" question from the "are all letters on the same row" question. The hash map handles the first part, and a simple all() check handles the second.
Step-by-step walkthrough
Let's trace through the input ["Hello", "Alaska", "Dad", "Peace"] using the hash map approach. For each word, we look up the row of every letter and check whether they all match.
Step 1: Check "Hello"
Letters span Rows 2, 1. Skip this word.
Step 2: Check "Alaska"
All letters on Row 2. Include in result.
Step 3: Check "Dad"
All letters on Row 2. Include in result.
Step 4: Check "Peace"
Letters span Rows 1, 2, 3. Skip this word.
Two words pass the test: "Alaska" (all Row 2) and "Dad" (all Row 3). "Hello" mixes Rows 2 and 1. "Peace" mixes Rows 1 and 2.
The code
def findWords(words):
row_map = {}
for i, row in enumerate("qwertyuiop asdfghjkl zxcvbnm".split()):
for ch in row:
row_map[ch] = i
result = []
for word in words:
lower = word.lower()
target = row_map[lower[0]]
if all(row_map[ch] == target for ch in lower):
result.append(word)
return result
The first loop builds the hash map. We split the three rows by space and assign each letter an index (0, 1, or 2). The second loop processes each word: convert to lowercase, grab the row of the first character, then use all() to confirm every character matches. If they all do, the word goes into the result list. Notice that we append the original word (with its original casing), not the lowered version.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Hash map lookup | O(n * k) | O(1) |
Where n is the number of words and k is the average word length. The hash map itself uses constant space since the alphabet has a fixed 26 letters. Each word requires checking every character once, giving O(k) per word and O(n * k) overall.
Building blocks
The reusable pattern here is hash map categorization. Whenever you need to classify items into groups and then filter or aggregate based on group membership, pre-building a lookup table is the cleanest approach. You will see this same technique in problems like Group Anagrams (where you categorize strings by sorted character signature) and problems that require mapping characters to frequency buckets.
The two-phase structure is worth internalizing: first, build the map; second, query the map. Keeping these phases separate makes the code easier to read and harder to get wrong.
Edge cases
Single-character words. A word like "I" or "a" trivially belongs to one row. Your code handles this naturally since the all() check passes immediately.
Mixed case. The problem states that words can contain uppercase and lowercase letters. Always convert to lowercase before looking up the row. Return the original word, not the lowered version.
Single word in the input. If words has only one element, you still need to check whether it belongs to a single row. Do not assume it does.
Do not forget to handle uppercase letters. "Alaska" starts with a capital "A", but "a" is on Row 2. Always normalize to lowercase before checking the row map.
All words pass. If every word in the input uses letters from a single row, you return the entire input. No special logic needed.
No words pass. If no word qualifies, you return an empty list. Again, the standard loop handles this without any extra conditions.
From understanding to recall
The hash map lookup pattern in this problem is one you will reuse constantly. The specific keyboard layout is not worth memorizing, but the technique of mapping items to categories using a dictionary and then filtering by category is fundamental. Practice building the map from scratch and writing the all() check. Once you can reproduce this solution without looking at notes, you have added a versatile building block to your toolkit.
Related posts
- Group Anagrams - another hash map categorization problem
- Isomorphic Strings - character mapping with hash maps
- Word Pattern - pattern matching with hash maps
The hash map categorization pattern shows up in many problems beyond keyboard rows. If you want to build deep fluency with these patterns, CodeBricks uses spaced repetition to help you move from understanding to automatic recall.