1. 为什么需要掌握输入模板?
在算法竞赛和在线编程平台中,输入处理往往是解题的第一步,也是最容易被忽视的环节。很多初学者在LeetCode上刷题时习惯了预设好的方法签名,一旦切换到ACM模式或实际面试的白板编程环节,面对原始输入数据就会手足无措。
我刚开始参加ACM竞赛时就深有体会——明明算法思路完全正确,却因为输入处理不当导致超时或错误。比如有一次遇到多组测试数据的情况,因为没有正确处理输入结束条件,程序陷入死循环直接爆零。这种教训让我意识到:输入处理不是简单的语法问题,而是算法实现的基础设施。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础输入输出类模板
2.1 Scanner类基础用法
Scanner是Java中最常用的输入工具,适合处理格式明确的输入数据。基本使用模式如下:
java复制import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取整数
int n = sc.nextInt();
// 读取字符串
String s = sc.next();
// 读取整行
String line = sc.nextLine();
sc.close();
}
}
注意:next()和nextLine()混用时容易出错。next()会留下换行符,紧接着的nextLine()会读取到空字符串。解决方法是在两者之间加一个额外的nextLine()消耗换行符。
2.2 BufferedReader高效读取
当输入数据量较大时(如10^5量级),Scanner的性能劣势就会显现。这时应该使用BufferedReader:
java复制import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 读取单行
String line = br.readLine();
// 分割字符串
String[] parts = line.split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
}
}
实测对比:处理10万个整数输入时,BufferedReader比Scanner快3-5倍。这是因为BufferedReader使用了缓冲机制,减少了底层I/O操作次数。
3. 常见输入模式解析
3.1 单组数据输入
这是最简单的输入模式,通常出现在LeetCode的ACM模式题目中:
code复制输入:
5
1 2 3 4 5
对应处理代码:
java复制Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
3.2 多组数据输入
ACM竞赛中更常见的模式,需要处理多组测试用例直到输入结束:
code复制输入:
3
1 2
3 4
5 6
处理方案:
java复制// 方法1:已知组数
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while (T-- > 0) {
int a = sc.nextInt();
int b = sc.nextInt();
// 处理逻辑
}
// 方法2:未知组数(直到EOF)
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int a = sc.nextInt();
int b = sc.nextInt();
// 处理逻辑
}
踩坑提醒:在线判题系统通常使用文件重定向输入,在本地测试时需要用Ctrl+Z(Windows)或Ctrl+D(Unix)模拟EOF。
4. 高级输入处理技巧
4.1 矩阵输入优化
处理二维矩阵时,直接使用双重循环可能导致性能问题。对于大型矩阵(如1000x1000),可以考虑以下优化:
java复制BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[][] matrix = new int[n][n];
for (int i = 0; i < n; i++) {
String[] parts = br.readLine().split(" ");
for (int j = 0; j < n; j++) {
matrix[i][j] = Integer.parseInt(parts[j]);
}
}
优化点:
- 使用BufferedReader替代Scanner
- 按行读取后分割,减少I/O次数
- 预分配矩阵空间,避免动态扩容
4.2 非固定格式输入处理
有时输入格式不规则,比如混合了数字和字符串:
code复制输入:
3
add 5
remove
query
处理策略:
java复制Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n-- > 0) {
String cmd = sc.next();
switch (cmd) {
case "add":
int num = sc.nextInt();
// 处理添加
break;
case "remove":
// 处理移除
break;
// 其他命令...
}
}
5. 输入模板实战应用
5.1 LeetCode ACM模式适配
很多LeetCode题目在转换为ACM模式时,输入处理需要特别注意。以两数之和为例:
标准模式:
java复制class Solution {
public int[] twoSum(int[] nums, int target) {
// 实现逻辑
}
}
ACM模式:
java复制public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取数组
String[] strNums = sc.nextLine().split(",");
int[] nums = new int[strNums.length];
for (int i = 0; i < strNums.length; i++) {
nums[i] = Integer.parseInt(strNums[i].trim());
}
// 读取目标值
int target = Integer.parseInt(sc.nextLine());
// 调用解法
int[] result = twoSum(nums, target);
System.out.println(Arrays.toString(result));
}
private static int[] twoSum(int[] nums, int target) {
// 相同实现
}
}
5.2 复杂数据结构输入
处理树结构输入时,常见的序列化格式有两种:
- 层序遍历表示(LeetCode风格):
code复制[3,9,20,null,null,15,7]
处理代码:
java复制String input = sc.nextLine().replaceAll("[\\[\\]]", "");
String[] values = input.split(",");
TreeNode root = buildTree(values);
// 建树方法
private static TreeNode buildTree(String[] values) {
if (values.length == 0 || values[0].equals("null")) return null;
// 实现层序建树逻辑...
}
- 父子关系表示(ACM竞赛常见):
code复制5
1 2 L
1 3 R
2 4 L
2 5 R
表示节点1的左子节点是2,右子节点是3,以此类推。
6. 性能优化与异常处理
6.1 输入缓冲区优化
对于超大规模输入(10^6级别),连BufferedReader都可能成为瓶颈。这时可以手动实现缓冲:
java复制InputStreamReader isr = new InputStreamReader(System.in);
char[] buffer = new char[1 << 16]; // 64KB缓冲区
int len = 0;
int pos = 0;
private int read() throws IOException {
if (pos >= len) {
len = isr.read(buffer);
pos = 0;
}
return buffer[pos++];
}
private int nextInt() throws IOException {
int num = 0;
int sign = 1;
int c = read();
while (c <= ' ') c = read();
if (c == '-') {
sign = -1;
c = read();
}
while (c >= '0' && c <= '9') {
num = num * 10 + (c - '0');
c = read();
}
return num * sign;
}
这种实现比标准库更快,但代码复杂度也更高,适合极端性能要求的场景。
6.2 输入异常处理
健壮的输入处理需要考虑各种异常情况:
java复制try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null) {
try {
String[] parts = line.split(" ");
if (parts.length < 2) {
System.err.println("输入格式错误:需要两个参数");
continue;
}
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
// 处理逻辑...
} catch (NumberFormatException e) {
System.err.println("输入必须为数字");
}
}
} catch (IOException e) {
e.printStackTrace();
}
7. 20种完整输入模板示例
7.1 单变量输入
java复制Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
double b = sc.nextDouble();
String c = sc.next();
7.2 多变量同行输入
java复制// 输入:"1 2 3"
String[] parts = sc.nextLine().split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
int c = Integer.parseInt(parts[2]);
7.3 数组输入
java复制int n = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
7.4 二维数组输入
java复制int rows = sc.nextInt();
int cols = sc.nextInt();
int[][] matrix = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = sc.nextInt();
}
}
7.5 不定长数组输入
java复制// 输入:"1,2,3,4"
String[] parts = sc.nextLine().split(",");
int[] arr = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
arr[i] = Integer.parseInt(parts[i]);
}
7.6 多组测试数据
java复制int T = sc.nextInt();
while (T-- > 0) {
int a = sc.nextInt();
int b = sc.nextInt();
// 处理每组数据
}
7.7 直到文件结束
java复制Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int a = sc.nextInt();
int b = sc.nextInt();
// 处理逻辑
}
7.8 带结束标志的输入
java复制// 输入以0结束
int n;
while ((n = sc.nextInt()) != 0) {
// 处理逻辑
}
7.9 字符串处理
java复制String s = sc.nextLine();
// 统计字符出现次数
int[] count = new int[256];
for (char c : s.toCharArray()) {
count[c]++;
}
7.10 混合类型输入
java复制String name = sc.next();
int age = sc.nextInt();
double score = sc.nextDouble();
7.11 日期时间输入
java复制// 输入:"2023-08-20 14:30:00"
String datetime = sc.nextLine();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(datetime);
7.12 链表输入
java复制// 输入:"1->2->3->4"
String[] parts = sc.nextLine().split("->");
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
for (String part : parts) {
curr.next = new ListNode(Integer.parseInt(part));
curr = curr.next;
}
ListNode head = dummy.next;
7.13 二叉树输入
java复制// 层序遍历输入:"1,2,3,null,4"
String[] parts = sc.nextLine().split(",");
Queue<TreeNode> queue = new LinkedList<>();
TreeNode root = new TreeNode(Integer.parseInt(parts[0]));
queue.offer(root);
int index = 1;
while (!queue.isEmpty() && index < parts.length) {
// 建树逻辑...
}
7.14 图输入(邻接表)
java复制int V = sc.nextInt();
int E = sc.nextInt();
List<Integer>[] adj = new List[V];
for (int i = 0; i < V; i++) {
adj[i] = new ArrayList<>();
}
for (int i = 0; i < E; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
adj[u].add(v);
adj[v].add(u); // 无向图
}
7.15 图输入(邻接矩阵)
java复制int V = sc.nextInt();
int[][] graph = new int[V][V];
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
graph[i][j] = sc.nextInt();
}
}
7.16 带权边输入
java复制int V = sc.nextInt();
int E = sc.nextInt();
List<int[]>[] adj = new List[V]; // int[0]=to, int[1]=weight
for (int i = 0; i < V; i++) {
adj[i] = new ArrayList<>();
}
for (int i = 0; i < E; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
int w = sc.nextInt();
adj[u].add(new int[]{v, w});
}
7.17 多行文本输入
java复制int n = sc.nextInt();
sc.nextLine(); // 消耗换行符
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(sc.nextLine()).append("\n");
}
String text = sb.toString();
7.18 交互式输入
java复制// 用于编程竞赛中的交互题
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
while (true) {
String query = br.readLine();
if (query.equals("EXIT")) break;
// 处理查询
String response = processQuery(query);
out.println(response);
out.flush(); // 重要!确保立即输出
}
7.19 文件输入重定向
java复制// 在main方法开始时添加这行代码
System.setIn(new FileInputStream("input.txt"));
// 然后正常使用Scanner或BufferedReader
7.20 自定义分隔符输入
java复制Scanner sc = new Scanner(System.in);
sc.useDelimiter("[,\\s]+"); // 使用逗号或空白作为分隔符
int a = sc.nextInt();
int b = sc.nextInt();
String c = sc.next();
8. 输入处理中的常见陷阱
8.1 缓冲区未清空问题
在混合使用不同输入方法时,经常会出现缓冲区残留问题。典型场景:
java复制int n = sc.nextInt();
String s = sc.nextLine(); // 这里会读取到空字符串
解决方案:
java复制int n = sc.nextInt();
sc.nextLine(); // 消耗换行符
String s = sc.nextLine(); // 现在能正确读取
8.2 数字格式异常处理
当输入可能包含非数字字符时,需要妥善处理:
java复制while (true) {
try {
int num = Integer.parseInt(sc.next());
break;
} catch (NumberFormatException e) {
System.out.println("请输入有效数字!");
}
}
8.3 大数据量输入优化
处理10^6级别的输入时,每个微小的优化都能带来显著性能提升。实测对比:
| 方法 | 处理100万整数时间 |
|---|---|
| Scanner.nextInt() | 1.8s |
| BufferedReader + split | 0.6s |
| 自定义缓冲读取 | 0.3s |
优化建议:
- 避免频繁的字符串分割
- 重用缓冲区
- 手动解析数字比库函数更快
9. 不同场景下的输入选择策略
9.1 算法竞赛场景
在ACM/ICPC等编程竞赛中:
- 首选BufferedReader + 手动解析
- 预先分配足够大的缓冲区(1MB左右)
- 避免使用正则表达式分割字符串
- 准备快速输入模板类(如下)
java复制static class FastReader {
BufferedReader br;
StringTokenizer st;
public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
}
String next() {
while (st == null || !st.hasMoreElements()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() { return Integer.parseInt(next()); }
// 其他类型方法...
}
9.2 面试白板编程场景
在技术面试中:
- 向面试官确认输入格式
- 假设使用Scanner简化代码
- 适当省略异常处理,专注于算法逻辑
- 可以注释说明输入处理部分
9.3 LeetCode自定义输入测试
当需要在LeetCode测试用例之外验证代码时:
- 复制官方测试用例
- 转换为标准输入格式
- 使用如下模板快速测试:
java复制public static void main(String[] args) {
String input = "[1,2,3,4,5]\n2"; // 模拟LeetCode输入
System.setIn(new ByteArrayInputStream(input.getBytes()));
// 然后正常编写解决方案代码
Solution solution = new Solution();
// 调用解法...
}
10. 输入模板的模块化与复用
为了提高编码效率,建议将常用输入模式封装成工具类:
java复制public class InputUtils {
private static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static int[] readIntArray() throws IOException {
String[] parts = br.readLine().split(" ");
int[] arr = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
arr[i] = Integer.parseInt(parts[i]);
}
return arr;
}
public static int[][] readIntMatrix(int rows, int cols) throws IOException {
int[][] matrix = new int[rows][cols];
for (int i = 0; i < rows; i++) {
matrix[i] = readIntArray();
}
return matrix;
}
// 其他工具方法...
}
使用时直接调用:
java复制int[] nums = InputUtils.readIntArray();
int[][] graph = InputUtils.readIntMatrix(n, n);
这种模块化设计可以:
- 减少重复代码
- 统一错误处理
- 方便后期维护
- 提高解题时的专注度
在实际编程竞赛中,我通常会准备一个包含20-30个常用方法的工具类,覆盖各种输入场景。这样在比赛时就能专注于算法逻辑,而不是反复调试输入处理。
