尚未讲过
字数 3148 2025-12-15 05:36:00
好的,我随机挑选一个尚未讲过的哈希算法相关题目,为你进行细致准确的讲解。
哈希算法题目:键盘行
题目描述
给你一个字符串数组 words,要求你找出并返回那些所有字母都在美式键盘的同一行上的单词。你可以按任意顺序返回答案。
美式键盘的字母行如下:
- 第一行:
"qwertyuiop" - 第二行:
"asdfghjkl" - 第三行:
"zxcvbnm"
示例 1:
输入:words = ["Hello","Alaska","Dad","Peace"]
输出:["Alaska","Dad"]
解释:
- “Hello” 中的 ‘h’ 和 ‘e’ 在第二行,但 ‘l’ 和 ‘o’ 在第一行,不符合。
- “Alaska” 所有字母都在第二行。
- “Dad” 所有字母都在第二行(注意大小写不敏感,’D’ 视为 ‘d’)。
- “Peace” 中的 ‘p’ 在第一行,其他字母在第二行,不符合。
示例 2:
输入:words = ["omk"]
输出:[]
示例 3:
输入:words = ["adsdf","sfd"]
输出:["adsdf","sfd"]
解题过程循序渐进讲解
这个问题非常适合使用哈希表来解决。核心思路是:为每个字母快速查询它属于键盘的哪一行,然后检查一个单词的所有字母是否属于同一行。
步骤一:理解需求与建立映射
题目要求判断单词字母是否在同一行。首先,我们需要建立一个从字母到行号的映射关系。
- 忽略大小写:键盘行定义的是小写字母,所以处理每个字符时,应统一转换为小写(或大写)后再进行判断。
- 创建哈希映射:最直接的方式是创建三个字符串,分别代表三行键盘字符。然后,为了快速查询,我们可以创建一个
Map(字典),键(key)是字母,值(value)是该字母所在的行号(例如 0, 1, 2)。 - 填充映射:
现在,# 伪代码示例 row_map = {} # 初始化一个空的哈希映射 # 第一行字母,行号设为 0 for char in “qwertyuiop”: row_map[char] = 0 # 第二行字母,行号设为 1 for char in “asdfghjkl”: row_map[char] = 1 # 第三行字母,行号设为 2 for char in “zxcvbnm”: row_map[char] = 2row_map[‘a’]会返回1,row_map[‘q’]会返回0。查询的时间复杂度是 O(1)。
步骤二:设计单词检查逻辑
对于一个给定的单词(如 “Alaska”),如何判断其所有字母是否在同一行?
- 获取首个字母的基准行号:将单词的第一个字母(转换为小写)作为基准,从
row_map中查出它的行号。- 例如 “Alaska” -> 第一个字母 ‘a’(小写) ->
row_map[‘a’]->1。
- 例如 “Alaska” -> 第一个字母 ‘a’(小写) ->
- 遍历后续字母:从单词的第二个字母开始,依次将每个字母(转换为小写)在
row_map中查询其行号。 - 进行比较:将当前字母的行号与基准行号进行比较。
- 如果所有后续字母的行号都与基准行号相等,那么这个单词满足条件。
- 如果有任何一个字母的行号与基准行号不相等,那么这个单词不满足条件,可以立即停止检查。
- 逻辑流程图:
开始检查单词 | 将单词首字母小写,并查询其行号 -> 设为 `base_row` | 遍历单词的剩余字母: | 将当前字母小写,查询其行号 -> 设为 `current_row` | ┌── current_row == base_row ? ──┐ │ │ │ 是 (继续) 否 (中断) │ ↓ ↓ 检查下一个字母 将此单词标记为不合格 | | ↓ ↓ (所有字母检查完毕) (结束) | ↓ 将此单词标记为合格
步骤三:整合算法与处理边界
现在,我们将步骤一和步骤二整合,并处理整个字符串数组。
- 预处理:在算法开始前,先构建好
row_map。这是一个一次性的操作。 - 主循环:遍历输入的
words数组,对每个单词执行步骤二的检查逻辑。 - 收集结果:创建一个结果列表
result。如果一个单词通过检查,就将其原样(保持原有大小写)添加到result中。 - 边界情况考虑:
- 空单词:题目通常不会给,但如果有,可以认为它 vacuously true(所有字母都在同一行)或直接跳过。为了安全,可以视为满足条件(因为不存在不同行的字母)。
- 单个字母的单词:它显然满足条件(只有一个字母,当然“所有”字母都在同一行)。
步骤四:代码实现与示例推演
让我们用示例一 ["Hello","Alaska","Dad","Peace"] 来推演。
- 构建
row_map:(略,如前所述) - 检查 “Hello”:
- 首字母
‘h’->row_map[‘h’]->1(第二行)。 - 检查
‘e’->row_map[‘e’]->1(相同,继续)。 - 检查
‘l’->row_map[‘l’]->0(第一行) -> 与基准 1 不同! -> 不合格,跳过。
- 首字母
- 检查 “Alaska”:
- 首字母
‘a’->row_map[‘a’]->1。 - 检查
‘l’->row_map[‘l’]->0-> 不同! -> 不合格,跳过。等等,这里好像不对? - 注意:我们需要将整个单词统一转为小写后再比较!
“Alaska”转为小写是“alaska”。 - 重新检查
“alaska”:- 首字母
‘a’-> 行号1。 - 检查
‘l’-> 行号0-> 不同 -> 不合格。是的,我最初举的例子“Alaska”中其实有个‘l’在第一行,所以它并不合格。看来我记忆有误,我们以示例的正确答案为准,“Alaska”应该是合格的。
- 首字母
- 纠正:让我们严格按示例来。
“Alaska”字母为A, l, a, s, k, a。小写后a, l, a, s, k, a。- 首字母
‘a’-> 行号1。 - 检查
‘l’-> 行号0-> 不同!等一下,根据键盘布局,字母‘l’在第二行asdfghjkl里!row_map[‘l’]应该是1,不是0。我之前的键盘行字符串写错了。 - 重要修正:
- 第二行是
“asdfghjkl”,包含‘l’。 - 第一行是
“qwertyuiop”,不包含‘l’。
- 第二行是
- 所以
row_map[‘l’] = 1。 - 继续检查
‘a’->1,‘s’->1,‘k’->1,‘a’->1。全部相同。因此“Alaska”合格。
- 首字母
- 首字母
- 检查 “Dad”:
- 小写
“dad”。首字母‘d’->row_map[‘d’]->1。 - 检查
‘a’->1,‘d’->1。合格。
- 小写
- 检查 “Peace”:
- 小写
“peace”。首字母‘p’->row_map[‘p’]->0(第一行)。 - 检查
‘e’->row_map[‘e’]->1(第二行) -> 不同!不合格。
- 小写
最终结果:[“Alaska”, “Dad”],与题目示例一致。
步骤五:复杂度分析
- 时间复杂度:
O(N * L),其中N是单词数组words的长度,L是单词的平均长度。- 构建
row_map是O(1),因为键盘字母总数是固定的 26。 - 对于每个单词,我们至多遍历其所有字符一次。哈希表查询操作是
O(1)。
- 构建
- 空间复杂度:
O(1)或O(26)。用于存储row_map的空间是固定大小的,与输入无关。结果列表result使用的空间是O(N),但这是输出所需的,通常不计入额外的空间复杂度。
总结
这道题巧妙利用了哈希表快速查找的特性。核心步骤是:
- 构建静态映射:用哈希表建立 26 个字母到其键盘行号的映射。
- 逐单词判断:对每个单词,先获取其首字母的行号作为基准,然后遍历后续字母,利用哈希表快速获取每个字母的行号并与基准比较。
- 大小写处理:在查询前统一将字母转换为小写,确保映射正确。
这种方法思路清晰,效率很高,是解决此类“归类判断”问题的典型哈希表应用。