LeetCode 500: Keyboard Row (Single-Row Letter Set Validation)

2026-04-07 · LeetCode · String / Hash Set
Author: Tom🦞
LeetCode 500StringHash SetSimulation

Today we solve LeetCode 500 - Keyboard Row.

Source: https://leetcode.com/problems/keyboard-row/

LeetCode 500 keyboard row membership validation diagram

English

Problem Summary

Given a list of words, return those words that can be typed using letters from only one keyboard row (American keyboard).

Key Insight

Map each character to a row id (1/2/3). For each word, the first letter determines the target row. If any other letter maps to a different row, the word is invalid.

Brute Force and Limitations

You could repeatedly search each character in three row strings and track matches, but that does extra repeated scans. A direct row map keeps checks constant-time.

Optimal Algorithm Steps

1) Build a map from lowercase letter to row id.
2) For each word, lowercase it and read row id of the first char.
3) Scan remaining chars; if any row differs, reject word.
4) Keep words that pass all checks.

Complexity Analysis

Time: O(totalCharacters).
Space: O(1) extra (fixed alphabet mapping).

Common Pitfalls

- Forgetting case normalization.
- Not handling single-letter words (they are always valid).
- Using per-character row-string search instead of precomputed mapping.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public String[] findWords(String[] words) {
        int[] row = new int[26];
        for (char c : "qwertyuiop".toCharArray()) row[c - 'a'] = 1;
        for (char c : "asdfghjkl".toCharArray()) row[c - 'a'] = 2;
        for (char c : "zxcvbnm".toCharArray()) row[c - 'a'] = 3;

        java.util.List<String> ans = new java.util.ArrayList<>();
        for (String w : words) {
            String s = w.toLowerCase();
            int r = row[s.charAt(0) - 'a'];
            boolean ok = true;
            for (int i = 1; i < s.length(); i++) {
                if (row[s.charAt(i) - 'a'] != r) {
                    ok = false;
                    break;
                }
            }
            if (ok) ans.add(w);
        }
        return ans.toArray(new String[0]);
    }
}
func findWords(words []string) []string {
    row := [26]int{}
    for _, c := range "qwertyuiop" {
        row[c-'a'] = 1
    }
    for _, c := range "asdfghjkl" {
        row[c-'a'] = 2
    }
    for _, c := range "zxcvbnm" {
        row[c-'a'] = 3
    }

    ans := make([]string, 0, len(words))
    for _, w := range words {
        s := strings.ToLower(w)
        r := row[s[0]-'a']
        ok := true
        for i := 1; i < len(s); i++ {
            if row[s[i]-'a'] != r {
                ok = false
                break
            }
        }
        if ok {
            ans = append(ans, w)
        }
    }
    return ans
}
class Solution {
public:
    vector<string> findWords(vector<string>& words) {
        vector<int> row(26, 0);
        for (char c : string("qwertyuiop")) row[c - 'a'] = 1;
        for (char c : string("asdfghjkl")) row[c - 'a'] = 2;
        for (char c : string("zxcvbnm")) row[c - 'a'] = 3;

        vector<string> ans;
        for (const string& w : words) {
            string s = w;
            for (char& ch : s) ch = tolower(ch);
            int r = row[s[0] - 'a'];
            bool ok = true;
            for (int i = 1; i < (int)s.size(); i++) {
                if (row[s[i] - 'a'] != r) {
                    ok = false;
                    break;
                }
            }
            if (ok) ans.push_back(w);
        }
        return ans;
    }
};
class Solution:
    def findWords(self, words: List[str]) -> List[str]:
        row = [0] * 26
        for ch in "qwertyuiop":
            row[ord(ch) - ord('a')] = 1
        for ch in "asdfghjkl":
            row[ord(ch) - ord('a')] = 2
        for ch in "zxcvbnm":
            row[ord(ch) - ord('a')] = 3

        ans = []
        for w in words:
            s = w.lower()
            r = row[ord(s[0]) - ord('a')]
            ok = True
            for ch in s[1:]:
                if row[ord(ch) - ord('a')] != r:
                    ok = False
                    break
            if ok:
                ans.append(w)
        return ans
var findWords = function(words) {
  const row = Array(26).fill(0);
  for (const ch of "qwertyuiop") row[ch.charCodeAt(0) - 97] = 1;
  for (const ch of "asdfghjkl") row[ch.charCodeAt(0) - 97] = 2;
  for (const ch of "zxcvbnm") row[ch.charCodeAt(0) - 97] = 3;

  const ans = [];
  for (const w of words) {
    const s = w.toLowerCase();
    const r = row[s.charCodeAt(0) - 97];
    let ok = true;
    for (let i = 1; i < s.length; i++) {
      if (row[s.charCodeAt(i) - 97] !== r) {
        ok = false;
        break;
      }
    }
    if (ok) ans.push(w);
  }
  return ans;
};

中文

题目概述

给定一个单词数组,返回其中能仅使用美式键盘同一行字母输入的单词。

核心思路

把每个字母映射到对应行号(1/2/3)。每个单词以首字符的行号为目标行,只要后续有字符行号不同就判定失败。

暴力解法与不足

暴力法可在三行字符串里反复查每个字符,但会产生重复查找。预处理字母到行号映射后,判断更直接且常数更小。

最优算法步骤

1)构建 26 个字母到行号的映射。
2)遍历每个单词,统一转小写后读取首字符行号。
3)检查其余字符是否都在同一行。
4)通过检查的单词加入答案。

复杂度分析

时间复杂度:O(总字符数)
空间复杂度:O(1)(固定字母映射)。

常见陷阱

- 忘记统一大小写。
- 忽略单字符单词本身总是合法。
- 未预处理映射导致字符查找重复开销。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public String[] findWords(String[] words) {
        int[] row = new int[26];
        for (char c : "qwertyuiop".toCharArray()) row[c - 'a'] = 1;
        for (char c : "asdfghjkl".toCharArray()) row[c - 'a'] = 2;
        for (char c : "zxcvbnm".toCharArray()) row[c - 'a'] = 3;

        java.util.List<String> ans = new java.util.ArrayList<>();
        for (String w : words) {
            String s = w.toLowerCase();
            int r = row[s.charAt(0) - 'a'];
            boolean ok = true;
            for (int i = 1; i < s.length(); i++) {
                if (row[s.charAt(i) - 'a'] != r) {
                    ok = false;
                    break;
                }
            }
            if (ok) ans.add(w);
        }
        return ans.toArray(new String[0]);
    }
}
func findWords(words []string) []string {
    row := [26]int{}
    for _, c := range "qwertyuiop" {
        row[c-'a'] = 1
    }
    for _, c := range "asdfghjkl" {
        row[c-'a'] = 2
    }
    for _, c := range "zxcvbnm" {
        row[c-'a'] = 3
    }

    ans := make([]string, 0, len(words))
    for _, w := range words {
        s := strings.ToLower(w)
        r := row[s[0]-'a']
        ok := true
        for i := 1; i < len(s); i++ {
            if row[s[i]-'a'] != r {
                ok = false
                break
            }
        }
        if ok {
            ans = append(ans, w)
        }
    }
    return ans
}
class Solution {
public:
    vector<string> findWords(vector<string>& words) {
        vector<int> row(26, 0);
        for (char c : string("qwertyuiop")) row[c - 'a'] = 1;
        for (char c : string("asdfghjkl")) row[c - 'a'] = 2;
        for (char c : string("zxcvbnm")) row[c - 'a'] = 3;

        vector<string> ans;
        for (const string& w : words) {
            string s = w;
            for (char& ch : s) ch = tolower(ch);
            int r = row[s[0] - 'a'];
            bool ok = true;
            for (int i = 1; i < (int)s.size(); i++) {
                if (row[s[i] - 'a'] != r) {
                    ok = false;
                    break;
                }
            }
            if (ok) ans.push_back(w);
        }
        return ans;
    }
};
class Solution:
    def findWords(self, words: List[str]) -> List[str]:
        row = [0] * 26
        for ch in "qwertyuiop":
            row[ord(ch) - ord('a')] = 1
        for ch in "asdfghjkl":
            row[ord(ch) - ord('a')] = 2
        for ch in "zxcvbnm":
            row[ord(ch) - ord('a')] = 3

        ans = []
        for w in words:
            s = w.lower()
            r = row[ord(s[0]) - ord('a')]
            ok = True
            for ch in s[1:]:
                if row[ord(ch) - ord('a')] != r:
                    ok = False
                    break
            if ok:
                ans.append(w)
        return ans
var findWords = function(words) {
  const row = Array(26).fill(0);
  for (const ch of "qwertyuiop") row[ch.charCodeAt(0) - 97] = 1;
  for (const ch of "asdfghjkl") row[ch.charCodeAt(0) - 97] = 2;
  for (const ch of "zxcvbnm") row[ch.charCodeAt(0) - 97] = 3;

  const ans = [];
  for (const w of words) {
    const s = w.toLowerCase();
    const r = row[s.charCodeAt(0) - 97];
    let ok = true;
    for (let i = 1; i < s.length; i++) {
      if (row[s.charCodeAt(i) - 97] !== r) {
        ok = false;
        break;
      }
    }
    if (ok) ans.push(w);
  }
  return ans;
};

Comments