500. Keyboard Row
Problem Description
You are given an array of strings words
. Your task is to find and return all words that can be typed using letters from only one row of an American keyboard.
The American keyboard has three rows of letter keys:
- First row:
"qwertyuiop"
- Second row:
"asdfghjkl"
- Third row:
"zxcvbnm"
A word can be typed using only one row if all the letters in that word (regardless of case) belong to the same keyboard row. The comparison is case-insensitive, meaning uppercase and lowercase versions of the same letter are considered to be on the same row.
For example:
- The word
"Hello"
cannot be typed using one row because 'h' is on the second row, 'e' is on the first row, and 'l' and 'o' are on different rows - The word
"Alaska"
can be typed using only the second row because all its letters ('a', 'l', 's', 'k') are found on the second row - The word
"Dad"
can be typed using only the second row because both 'd' and 'a' are on the second row
The solution approach creates three sets containing the letters from each keyboard row. For each word in the input array, it converts the word to lowercase and creates a set of its characters. If this character set is a subset of any of the three row sets (checked using the <=
operator), it means all letters of the word belong to that single row, so the word is added to the result list.
Intuition
The key insight is recognizing this as a set membership problem. When we need to check if a word can be typed using only one keyboard row, we're essentially asking: "Do all letters in this word belong to the same row?"
This naturally leads us to think about sets. Each keyboard row can be represented as a set of characters. For any given word, if we convert it to a set of characters, we can check whether this word's character set is completely contained within one of the row sets.
The mathematical relationship we're looking for is the subset relationship. If word_characters ⊆ row_characters
, then all letters in the word exist in that row. In Python, this subset check is elegantly expressed using the <=
operator between sets.
Why convert the word to lowercase? Since the problem states that typing is case-insensitive (both 'A' and 'a' are on the same key), we normalize everything to lowercase to simplify our comparison. This way, we don't need to store both uppercase and lowercase versions of each letter in our row sets.
The beauty of this approach is its simplicity - we transform the problem from "checking each character's row and ensuring they're all the same" to "checking if a word's character set is a subset of any row set". This reduces what could be a complex character-by-character validation into three simple subset checks using s <= s1 or s <= s2 or s <= s3
.
Solution Approach
The implementation follows a straightforward set-based approach:
1. Initialize Row Sets
s1 = set('qwertyuiop')
s2 = set('asdfghjkl')
s3 = set('zxcvbnm')
We create three sets, each containing the characters from one keyboard row. Using sets gives us O(1) lookup time for membership checking and built-in subset operations.
2. Process Each Word
ans = [] for w in words:
We initialize an empty result list and iterate through each word in the input array.
3. Normalize and Convert to Set
s = set(w.lower())
For each word, we:
- Convert it to lowercase to handle case-insensitivity
- Transform it into a set of characters, which automatically removes duplicates and prepares it for subset comparison
4. Check Subset Relationship
if s <= s1 or s <= s2 or s <= s3: ans.append(w)
The <=
operator in Python checks if the left set is a subset of the right set. If the word's character set is a subset of any row set, it means all letters in the word come from that single row. When this condition is true, we add the original word (preserving its original case) to our answer list.
5. Return Results
return ans
After processing all words, we return the list of words that can be typed using only one keyboard row.
Time Complexity: O(n × m) where n is the number of words and m is the average length of each word (for converting to set)
Space Complexity: O(1) for the row sets (constant size) plus O(k) for the answer list where k is the number of valid words
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the input: words = ["Hello", "Alaska", "Dad", "Peace"]
Step 1: Initialize keyboard row sets
s1 = {'q','w','e','r','t','y','u','i','o','p'}
s2 = {'a','s','d','f','g','h','j','k','l'}
s3 = {'z','x','c','v','b','n','m'}
Step 2: Process each word
Word 1: "Hello"
- Convert to lowercase:
"hello"
- Create character set:
s = {'h','e','l','o'}
- Check subsets:
- Is
{'h','e','l','o'} ⊆ s1
? No (h is not in row 1) - Is
{'h','e','l','o'} ⊆ s2
? No (e and o are not in row 2) - Is
{'h','e','l','o'} ⊆ s3
? No (h, e, l, o are not in row 3)
- Is
- Result: Not added to answer
Word 2: "Alaska"
- Convert to lowercase:
"alaska"
- Create character set:
s = {'a','l','s','k'}
- Check subsets:
- Is
{'a','l','s','k'} ⊆ s1
? No (none of these are in row 1) - Is
{'a','l','s','k'} ⊆ s2
? Yes! All letters are in row 2
- Is
- Result: "Alaska" added to answer
Word 3: "Dad"
- Convert to lowercase:
"dad"
- Create character set:
s = {'d','a'}
- Check subsets:
- Is
{'d','a'} ⊆ s1
? No - Is
{'d','a'} ⊆ s2
? Yes! Both letters are in row 2
- Is
- Result: "Dad" added to answer
Word 4: "Peace"
- Convert to lowercase:
"peace"
- Create character set:
s = {'p','e','a','c'}
- Check subsets:
- Is
{'p','e','a','c'} ⊆ s1
? No (a and c are not in row 1) - Is
{'p','e','a','c'} ⊆ s2
? No (p, e, c are not in row 2) - Is
{'p','e','a','c'} ⊆ s3
? No (p, e, a are not in row 3)
- Is
- Result: Not added to answer
Final Output: ["Alaska", "Dad"]
Solution Implementation
1class Solution:
2 def findWords(self, words: List[str]) -> List[str]:
3 # Define sets for each row of the QWERTY keyboard
4 first_row = set('qwertyuiop')
5 second_row = set('asdfghjkl')
6 third_row = set('zxcvbnm')
7
8 # List to store words that can be typed using letters from a single row
9 result = []
10
11 # Check each word in the input list
12 for word in words:
13 # Convert word to lowercase and create a set of its characters
14 word_chars = set(word.lower())
15
16 # Check if all characters belong to any single keyboard row
17 # The <= operator checks if word_chars is a subset of the row
18 if (word_chars <= first_row or
19 word_chars <= second_row or
20 word_chars <= third_row):
21 # Add the original word (with original casing) to result
22 result.append(word)
23
24 return result
25
1class Solution {
2 public String[] findWords(String[] words) {
3 // Mapping of each letter (a-z) to keyboard row number (0, 1, or 2)
4 // Each character position corresponds to a letter: index 0 = 'a', index 1 = 'b', etc.
5 // '0' = first row (qwertyuiop), '1' = second row (asdfghjkl), '2' = third row (zxcvbnm)
6 String keyboardRowMapping = "12210111011122000010020202";
7
8 // List to store words that can be typed using letters from only one keyboard row
9 List<String> validWords = new ArrayList<>();
10
11 // Check each word in the input array
12 for (String word : words) {
13 // Convert word to lowercase for case-insensitive comparison
14 String lowercaseWord = word.toLowerCase();
15
16 // Get the keyboard row of the first character
17 char targetRow = keyboardRowMapping.charAt(lowercaseWord.charAt(0) - 'a');
18
19 // Flag to track if all characters are in the same row
20 boolean isValidWord = true;
21
22 // Check if all characters in the word belong to the same keyboard row
23 for (char character : lowercaseWord.toCharArray()) {
24 if (keyboardRowMapping.charAt(character - 'a') != targetRow) {
25 isValidWord = false;
26 break;
27 }
28 }
29
30 // Add the original word to result if all characters are in the same row
31 if (isValidWord) {
32 validWords.add(word);
33 }
34 }
35
36 // Convert list to array and return
37 return validWords.toArray(new String[0]);
38 }
39}
40
1class Solution {
2public:
3 vector<string> findWords(vector<string>& words) {
4 // Map each letter (a-z) to its keyboard row number
5 // Row 0: qwertyuiop
6 // Row 1: asdfghjkl
7 // Row 2: zxcvbnm
8 string keyboardRowMapping = "12210111011122000010020202";
9
10 vector<string> result;
11
12 // Check each word to see if all letters are from the same row
13 for (const auto& word : words) {
14 // Get the row number of the first character
15 char targetRow = keyboardRowMapping[tolower(word[0]) - 'a'];
16
17 // Check if all characters in the word belong to the same row
18 bool isValidWord = true;
19 for (const char& currentChar : word) {
20 // Convert character to lowercase and get its row number
21 char currentRow = keyboardRowMapping[tolower(currentChar) - 'a'];
22
23 // If any character is from a different row, mark as invalid
24 if (currentRow != targetRow) {
25 isValidWord = false;
26 break;
27 }
28 }
29
30 // Add the word to result if all characters are from the same row
31 if (isValidWord) {
32 result.emplace_back(word);
33 }
34 }
35
36 return result;
37 }
38};
39
1/**
2 * Finds words that can be typed using letters from only one row of a keyboard
3 * @param words - Array of words to check
4 * @returns Array of words that can be typed using only one keyboard row
5 */
6function findWords(words: string[]): string[] {
7 // Mapping of each letter (a-z) to its keyboard row (0, 1, or 2)
8 // Row 0: qwertyuiop
9 // Row 1: asdfghjkl
10 // Row 2: zxcvbnm
11 const keyboardRowMapping: string = '12210111011122000010020202';
12 const validWords: string[] = [];
13
14 // Check each word in the input array
15 for (const word of words) {
16 // Convert word to lowercase for case-insensitive comparison
17 const lowercaseWord: string = word.toLowerCase();
18
19 // Get the row number of the first character
20 const firstCharIndex: number = lowercaseWord.charCodeAt(0) - 'a'.charCodeAt(0);
21 const targetRow: string = keyboardRowMapping[firstCharIndex];
22
23 // Check if all characters belong to the same row
24 let isValidWord: boolean = true;
25 for (const char of lowercaseWord) {
26 const charIndex: number = char.charCodeAt(0) - 'a'.charCodeAt(0);
27 if (keyboardRowMapping[charIndex] !== targetRow) {
28 isValidWord = false;
29 break;
30 }
31 }
32
33 // Add the original word if all characters are from the same row
34 if (isValidWord) {
35 validWords.push(word);
36 }
37 }
38
39 return validWords;
40}
41
Time and Space Complexity
Time Complexity: O(n * m)
where n
is the number of words in the input list and m
is the average length of each word.
- Creating the three sets
s1
,s2
,s3
takesO(1)
time since they contain a fixed number of characters - The main loop iterates through all
n
words - For each word:
- Converting to lowercase takes
O(m)
time wherem
is the length of the word - Creating a set from the lowercase word takes
O(m)
time - The subset check (
<=
) operation takesO(len(s))
time, which is at mostO(m)
sinces
contains unique characters from the word - Appending to the answer list takes
O(1)
amortized time
- Converting to lowercase takes
- Overall:
O(n * m)
Space Complexity: O(n * m)
in the worst case
- The three keyboard row sets
s1
,s2
,s3
useO(1)
space (fixed size) - For each word, we create a temporary set
s
which usesO(m)
space wherem
is the length of the word - The answer list
ans
can contain at mostn
words, and storing these words requiresO(n * m)
space in total - Overall:
O(n * m)
dominated by the space needed to store the output
Common Pitfalls
1. Forgetting Case-Insensitive Comparison
One of the most common mistakes is forgetting to handle uppercase letters properly. The keyboard row sets only contain lowercase letters, so comparing uppercase letters directly will fail.
Incorrect Code:
def findWords(self, words: List[str]) -> List[str]:
first_row = set('qwertyuiop')
second_row = set('asdfghjkl')
third_row = set('zxcvbnm')
result = []
for word in words:
# WRONG: Not converting to lowercase
word_chars = set(word) # This will fail for words like "Alaska"
if (word_chars <= first_row or
word_chars <= second_row or
word_chars <= third_row):
result.append(word)
return result
Why it fails: The word "Alaska" contains uppercase 'A' which won't match lowercase 'a' in the row sets, causing valid words to be incorrectly rejected.
Solution: Always convert the word to lowercase before creating the character set:
word_chars = set(word.lower()) # Correct approach
2. Modifying the Original Word Instead of Preserving Case
Another pitfall is returning the lowercase version of the word instead of preserving the original casing.
Incorrect Code:
def findWords(self, words: List[str]) -> List[str]:
first_row = set('qwertyuiop')
second_row = set('asdfghjkl')
third_row = set('zxcvbnm')
result = []
for word in words:
word_lower = word.lower() # Store lowercase version
word_chars = set(word_lower)
if (word_chars <= first_row or
word_chars <= second_row or
word_chars <= third_row):
result.append(word_lower) # WRONG: Appending lowercase version
return result
Why it fails: The problem requires returning words in their original form. If the input is ["Hello", "Alaska"], we should return ["Alaska"], not ["alaska"].
Solution: Always append the original word to the result:
result.append(word) # Preserve original casing
3. Using Incorrect Set Operations
Some might try to use intersection or other set operations incorrectly instead of the subset check.
Incorrect Code:
# WRONG: Checking if there's ANY overlap, not if ALL characters are in the row if word_chars & first_row: # This checks for any intersection result.append(word)
Why it fails: The intersection operator &
returns a non-empty set if there's any overlap. The word "Hello" would incorrectly pass for the first row because 'e' and 'o' are in the first row, even though 'h' and 'l' are not.
Solution: Use the subset operator <=
to ensure ALL characters are in the same row:
if word_chars <= first_row: # Correct: checks if word_chars is a subset
Which of the following is a min heap?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!