九宫格输入法模拟
问题背景
我们需要模拟一个经典的手机九宫格输入法。该输入法包含数字和英文两种输入模式,用户通过按下一系列按键(包括数字键、模式切换键和停顿键)来输入一个字符串。
输入模式与状态
-
数字模式 (Numeric Mode) :
- 这是输入法的初始默认状态。
- 在此模式下,按下任一数字键
0-9,会直接输出对应的数字。例如,按1输出1。
-
英文模式 (English Mode) :
- 通过特定按键(
#)可以切换到此模式。 - 在此模式下,按下数字键会根据其对应的英文字符集进行输入。
- 通过特定按键(
按键映射与规则 (英文模式下)
| 按键 | 对应的字符(循环) |
|---|---|
| 0 | ``(空格) |
| 1 | . , ? ! |
| 2 | a b c |
| 3 | d e f |
| 4 | g h i |
| 5 | j k l |
| 6 | m n o |
| 7 | p q r s |
| 8 | t u v |
| 9 | w x y z |
-
连续按键: 连续、快速地按同一个数字键,会依次选择其对应的字符。例如,在英文模式下,按一下
2选择a,再按一下选择b,第三下选择c,第四下则循环回到a。 -
字符上屏 (Commit) : 当一个字符被选中后,在以下三种情况下,该字符会被最终确认并“上屏”(添加到结果字符串中):
- 按下了不同的按键: 例如,先按了
22(选中b),紧接着按了3,此时b会被上屏。 - 按下了停顿符
!: 例如,22!,b会被上屏。 - 输入结束:整个按键序列处理完毕时,最后一个被选中的字符会自动上屏。
- 按下了不同的按键: 例如,先按了
输入指令
0-9: 数字键,其行为取决于当前模式。#: 模式切换键。每次按下,就在“数字模式”和“英文模式”之间切换一次。!: 停顿符。在英文模式下,用于中断连续按键并上屏当前选中的字符。
任务要求
给定一个表示用户按键操作的字符串 inputStr,请模拟输入法的完整行为,并输出最终显示在屏幕上的字符串。
输入格式
-
inputStr: 一个字符串,代表用户的系列按键操作。1 <= inputStr.length <= 200- 字符串仅由
0-9、#、!组成。
输出格式
- 一个字符串,表示模拟输入法后实际显示出来的内容。
样例说明
样例 1
-
输入:
"123" -
输出:
"123" -
解释:
- 初始状态: 数字模式。
- 按下
1-> 输出1。 - 按下
2-> 输出2。 - 按下
3-> 输出3。 - 最终结果为
"123"。
样例 2
-
输入:
"#22!23044444411" -
输出:
"bad i." -
解释:
#: 切换到 英文模式。22: 连续按键。第1下选中a,第2下选中b。!: 停顿。将当前选中的b上屏。输出: "b"2: 按一下2,选中a。3: 按下了不同的键。将之前选中的a上屏。然后处理3,选中d。输出: "ba"0: 按下了不同的键。将之前选中的d上屏。然后处理0,选中 ``(空格)。输出: "bad"4...4(共6次): 按下了不同的键。将之前选中的 ``上屏。然后处理4。连续按6次,g->h->i->g->h->i。最终选中i。输出: "bad "11: 按下了不同的键。将之前选中的i上屏。然后处理1。连续按2次,.->,。最终选中,。输出: "bad i"- 输入结束: 将最后选中的字符
,上屏。 最终输出: "bad i,"
import java.util.Scanner;
/**
* 解决“手机九宫格输入法”问题的方案类。
*/
public class Solution {
// --- Keypad Mapping ---
// 使用一个静态常量数组来映射数字键到其对应的字符序列。
// 索引 i 对应数字键 'i'。
// 样例中 "11" -> ".",说明 '1' 键的映射包含多个字符,这里采用一种常见的旧手机布局。
private static final String[] KEY_MAP = {
" ", // 0
".,?!", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz" // 9
};
// --- State Variables ---
// 将这些状态作为类的成员变量,方便在多个方法中共享和修改。
private StringBuilder resultBuilder; // 用于构建最终输出的字符串
private char lastPressedKey; // 英文模式下,上一个按下的数字键
private int pressCount; // 对应 lastPressedKey 的连续按键次数
/**
* 主逻辑方法,模拟输入过程并返回最终显示的字符串。
*
* @param inputStr 包含所有操作的命令字符串。
* @return 实际显示出来的字符串。
*/
public String simulateInput(String inputStr) {
// --- 1. 初始化状态 ---
this.resultBuilder = new StringBuilder();
this.lastPressedKey = '\0'; // 使用空字符 '\0' 表示没有正在处理的按键序列
this.pressCount = 0;
boolean isDigitMode = true; // 初始默认为「数字状态」
// --- 2. 遍历命令字符串 ---
for (char cmd : inputStr.toCharArray()) {
if (cmd == '#') { // 模式切换
// 在切换模式前,必须先将当前正在输入的字母“确定”下来
finalizeLetter();
isDigitMode = !isDigitMode; // 切换模式
} else if (cmd == '!') { // 停顿
// 停顿的作用就是中断连续按键,确定当前字母
finalizeLetter();
} else if (Character.isDigit(cmd)) { // 按下数字键
if (isDigitMode) {
// --- 数字状态 ---
// 在数字状态下,任何数字键的按下都会中断之前的字母输入序列
finalizeLetter();
// 直接追加数字字符
resultBuilder.append(cmd);
} else {
// --- 英文状态 ---
// 如果当前按键与上一个相同
if (cmd == lastPressedKey) {
pressCount++; // 增加连续按键计数
} else {
// 如果按键不同,说明上一个字母序列结束了
finalizeLetter(); // 先“确定”并输出上一个字母
// 然后开始一个新的字母序列
lastPressedKey = cmd;
pressCount = 1;
}
}
}
}
// --- 3. 最终处理 ---
// 循环结束后,如果最后的操作是一串连续的字母按键,它们尚未被“确定”
// 需要在这里进行最后一次处理
finalizeLetter();
// 返回构建好的完整字符串
return resultBuilder.toString();
}
/**
* 辅助方法:确定并输出当前正在处理的英文字符。
* 在按键序列被中断时(如按下不同键、'#'、'!' 或输入结束)调用。
*/
private void finalizeLetter() {
// 如果 pressCount 为 0,说明没有待处理的按键序列
if (pressCount == 0) {
return;
}
// 1. 根据按下的键,从 KEY_MAP 中获取候选字符字符串
int keyIndex = lastPressedKey - '0'; // '2' -> 2
String candidates = KEY_MAP[keyIndex];
// 2. 计算最终选择哪个字符
// (pressCount - 1) 是因为按1次对应索引0,按2次对应索引1...
// % candidates.length() 处理循环选择,例如按4次'2'键,(4-1)%3=0,回到'a'
int charIndex = (pressCount - 1) % candidates.length();
// 3. 将选定的字符追加到结果中
resultBuilder.append(candidates.charAt(charIndex));
// 4. 重置状态,为下一个按键序列做准备
lastPressedKey = '\0';
pressCount = 0;
}
}
/**
* 用于处理 ACM 风格输入输出的主类。
*/
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String inputStr = scanner.nextLine(); // 读取指令字符串
scanner.close();
// 创建 Solution 类的实例并调用方法
Solution solution = new Solution();
String result = solution.simulateInput(inputStr);
// 输出最终结果
System.out.println(result);
}
}