LeetCode 925. 长按键入
题目描述

题意分析
判断
typed是否可能由输入name时长按某些按键得到。每个源字符至少输入一次,长按只能让它在原位置连续重复,不能漏字、换序或插入其他字符。等价地说,两串的连续字符段必须按相同顺序出现,且
typed每段的长度不能少于name对应段。可以用双指针在扫描过程中完成这个判断,不必显式拆分各段。
解法:双指针扫描
核心思路
[!blue]
i指向name中下一个需要匹配的字符,j从左到右扫描typed。若typed[j] == name[i],优先把它用于正常输入,同时推进两个指针;同一字符段里,先满足源串必需的次数不会损失解,因为余下相同字符仍然可以解释为长按。若不能正常匹配,当前字符只能是上一按键的额外重复。因此必须满足
j>0且typed[j] == typed[j-1],此时只推进j。已经接受的前缀保证前一个字符有合法来源,继续重复它也合法;否则当前字符既不是下一个源字符,也不是延续前一次输入,只能返回false。每轮之后,已扫描的
typed前缀都有合法解释,i是按正常匹配优先能够覆盖的最长源前缀。若某个源字符段所需次数不足,扫描进入下一个不同字符时就无法通过匹配;如果不足发生在末尾,则会由最终的i == name.length()检查发现。只有实际输入和源串都处理完,才能返回true。
解题步骤
- 两个指针从头开始。
- 当前字符能正常匹配时,同时推进。
- 不能正常匹配时,只允许重复 typed 的前一字符。
- 扫描结束检查 name 是否已全部匹配。
首个输入字符没有前驱,只能正常匹配。源串匹配完后,实际输入仍可能有末尾长按,因此要继续扫描,但必须先检查
i的范围,不能再直接访问name[i]。
代码实现
class Solution {
public boolean isLongPressedName(String name, String typed) {
int i = 0;
int j = 0;
while (j < typed.length()) {
// 能正常匹配时先消费源字符,避免把必需重复误当作长按。
if (i < name.length() && name.charAt(i) == typed.charAt(j)) {
i++;
j++;
} else if (j > 0 && typed.charAt(j) == typed.charAt(j - 1)) {
j++;
} else {
return false;
}
}
// 实际输入已解释完,还要确认源串全部被输入。
return i == name.length();
}
}
func isLongPressedName(name string, typed string) bool {
i, j := 0, 0
for j < len(typed) {
// 能正常匹配时先消费源字符,避免把必需重复误当作长按。
if i < len(name) && name[i] == typed[j] {
i++
j++
} else if j > 0 && typed[j] == typed[j-1] {
j++
} else {
return false
}
}
// 实际输入已解释完,还要确认源串全部被输入。
return i == len(name)
}
复杂度分析
- 时间复杂度:$O(\lvert typed\rvert+1)$,每轮推进实际输入指针。
- 空间复杂度:$O(1)$。
关键点总结
[!green]
- 正常匹配优先,保证源串本来就需要的重复字符得到足够输入。
- 额外字符只能延续上一段,不能单凭源串是子序列就判定成功。
- 扫描结束还要检查源串是否全部匹配,防止尾部漏字。
易错点总结
[!yellow]
- 先把重复字符解释为长按,可能少消费源串本身需要的同字符。
- 不检查
j>0就读取前一个输入字符,首位会越界。- 源串用完后立即拒绝余下字符,会漏掉合法的末尾长按;直接接受余下字符又可能放过新字符。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 809. 情感丰富的文字 | 中等 | 同样逐段比较字符与游程长度,原题的拉伸还要求长段达到特定长度,本题只要求键入次数不少于原名。 |
| 443. 压缩字符串 | 中等 | 游程编码提供相同字符段及次数,本题比较两串对应段是否兼容长按。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!