The "Keyboard Row" problem asks you to filter a list of words and return only those that can be typed using letters from a single row of an American QWERTY keyboard. The keyboard rows are as follows:
q w e r t y u i o p
a s d f g h j k l
z x c v b n m
Given an array of strings words
, you must return a new array containing only those words that can be typed by using letters from one row on the keyboard. Each word in the output should satisfy this requirement individually. The comparison should be case-insensitive, and you do not need to consider empty words.
Constraints:
The first instinct might be to check, for each word, whether all its letters are present in any one of the keyboard rows. This could be done by iterating over each row and checking if the set of letters in the word is a subset of the set of letters in the row.
However, to optimize, we can avoid checking all rows for every word. Instead, we can map every letter to its corresponding row number. This way, for each word, we can quickly check if all its letters map to the same row. This approach reduces unnecessary checks and makes the solution more efficient.
The main challenge is ensuring case-insensitive comparison and efficiently associating each letter with its row.
Let's break down the steps for an efficient solution:
This method is efficient because it uses direct lookups to associate each letter with its row, and checks each word only once.
Let's consider the input ["Hello", "Alaska", "Dad", "Peace"]
.
Output: ["Alaska", "Dad"]
Brute-force approach:
n
words with average length k
, the time complexity is O(n * k * r), where r is the number of rows (3).Thus, the optimized solution is linear in the total number of letters across all words.
The "Keyboard Row" problem is efficiently solved by mapping each letter to its keyboard row and checking if all letters in a word belong to the same row. This approach leverages constant-time lookups for each letter, resulting in a linear-time solution. The key insight is to avoid redundant row checks by precomputing letter-to-row mappings, making the algorithm both simple and efficient.
class Solution:
def findWords(self, words):
row1 = set('qwertyuiop')
row2 = set('asdfghjkl')
row3 = set('zxcvbnm')
result = []
for word in words:
lower = set(word.lower())
if lower <= row1 or lower <= row2 or lower <= row3:
result.append(word)
return result
class Solution {
public:
vector<string> findWords(vector<string>& words) {
vector<string> res;
string rows[] = {"qwertyuiop", "asdfghjkl", "zxcvbnm"};
unordered_map<char, int> m;
for (int i = 0; i < 3; ++i)
for (char c : rows[i])
m[c] = i, m[toupper(c)] = i;
for (string word : words) {
int row = m[word[0]];
bool valid = true;
for (char c : word)
if (m[c] != row) {
valid = false;
break;
}
if (valid) res.push_back(word);
}
return res;
}
};
class Solution {
public String[] findWords(String[] words) {
String row1 = "qwertyuiop";
String row2 = "asdfghjkl";
String row3 = "zxcvbnm";
List<String> result = new ArrayList<>();
for (String word : words) {
String lower = word.toLowerCase();
if (allInRow(lower, row1) || allInRow(lower, row2) || allInRow(lower, row3)) {
result.add(word);
}
}
return result.toArray(new String[0]);
}
private boolean allInRow(String word, String row) {
for (char c : word.toCharArray()) {
if (row.indexOf(c) == -1) return false;
}
return true;
}
}
var findWords = function(words) {
const rows = [
new Set('qwertyuiop'),
new Set('asdfghjkl'),
new Set('zxcvbnm')
];
let result = [];
for (let word of words) {
let lower = word.toLowerCase();
for (let row of rows) {
if ([...lower].every(ch => row.has(ch))) {
result.push(word);
break;
}
}
}
return result;
};