1. ACM模式下的Java算法输入输出处理
作为一名经历过无数次算法面试的老兵,我深知ACM模式下的输入输出处理是很多Java选手的痛点。不同于LeetCode那种直接提供方法签名的模式,ACM模式要求我们自己处理原始输入数据,这对基本功的要求更高。今天我就把自己多年积累的实战经验整理成这份指南,涵盖数组、链表、二叉树等常见数据结构的处理模板。
2. 数组类输入处理
2.1 给定长度的数组输入
这是最常见的输入形式,先给出数组长度n,然后是n个元素。比如题目要求处理一个整数数组:
java复制import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 先读数组长度n
int n = sc.nextInt();
int[] arr = new int[n];
// 循环读n个元素
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
// 处理逻辑(这里以直接输出为例)
printArray(arr);
sc.close();
}
// 辅助方法:输出数组(避免最后一个空格)
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
if (i > 0) System.out.print(" ");
System.out.print(arr[i]);
}
System.out.println();
}
}
注意:Scanner的nextInt()方法会跳过空白字符读取下一个整数,包括空格、制表符和换行符。
2.2 不定长度的数组输入
有时候输入只给出一行数字,没有明确长度。这时我们需要先读取整行再分割:
java复制import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读一整行,按空格分割
String line = sc.nextLine();
String[] strs = line.split(" ");
// 转成int数组
int[] arr = new int[strs.length];
for (int i = 0; i < strs.length; i++) {
arr[i] = Integer.parseInt(strs[i]);
}
// 处理逻辑
printArray(arr);
sc.close();
}
}
常见坑点:混合使用nextInt()和nextLine()时,要注意nextInt()不会消耗行尾的换行符,需要在中间加一个sc.nextLine()来吸收多余的换行符。
3. 链表类输入处理
3.1 链表节点定义
首先必须自定义链表节点类:
java复制class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
this.next = null;
}
}
3.2 链表构建与输出
输入通常是一行用空格分隔的节点值:
java复制import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读输入,构建链表
String line = sc.nextLine();
String[] strs = line.split(" ");
ListNode head = buildList(strs);
// 处理逻辑(这里以直接输出为例)
printList(head);
sc.close();
}
// 辅助方法:构建链表
public static ListNode buildList(String[] strs) {
if (strs.length == 0) return null;
ListNode head = new ListNode(Integer.parseInt(strs[0]));
ListNode cur = head;
for (int i = 1; i < strs.length; i++) {
cur.next = new ListNode(Integer.parseInt(strs[i]));
cur = cur.next;
}
return head;
}
// 辅助方法:输出链表
public static void printList(ListNode head) {
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val);
if (cur.next != null) System.out.print("→");
cur = cur.next;
}
System.out.println();
}
}
实战技巧:构建链表时一定要维护一个当前节点指针cur,每次添加新节点后要移动cur到新节点。新手常犯的错误是忘记移动cur导致所有节点都连在头节点上。
4. 二叉树类输入处理
4.1 二叉树节点定义
java复制class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
4.2 层序构建二叉树
二叉树通常用层序数组输入,null表示空节点:
java复制import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
String[] strs = line.split(" ");
// 层序构建二叉树
TreeNode root = buildTree(strs);
// 处理逻辑(这里以层序输出为例)
levelOrderPrint(root);
sc.close();
}
// 辅助方法:层序构建二叉树
public static TreeNode buildTree(String[] strs) {
if (strs.length == 0 || strs[0].equals("null")) return null;
TreeNode root = new TreeNode(Integer.parseInt(strs[0]));
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int i = 1;
while (!queue.isEmpty() && i < strs.length) {
TreeNode parent = queue.poll();
// 处理左子节点
if (!strs[i].equals("null")) {
parent.left = new TreeNode(Integer.parseInt(strs[i]));
queue.offer(parent.left);
}
i++;
// 处理右子节点
if (i < strs.length && !strs[i].equals("null")) {
parent.right = new TreeNode(Integer.parseInt(strs[i]));
queue.offer(parent.right);
}
i++;
}
return root;
}
// 辅助方法:层序输出
public static void levelOrderPrint(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
System.out.print(node.val + " ");
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
System.out.println();
}
}
重要细节:构建二叉树时使用队列来维护当前层的节点,每次处理一个父节点时,按顺序处理它的左右子节点。空节点("null")需要跳过不处理。
5. 字符串类输入处理
5.1 多个字符串输入(给定数量)
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[] strs = new String[n];
for (int i = 0; i < n; i++) {
strs[i] = sc.next(); // next()读单个字符串(空格分隔)
}
// 输出
for (int i = 0; i < strs.length; i++) {
if (i > 0) System.out.print(" ");
System.out.print(strs[i]);
}
sc.close();
}
}
5.2 带空格的字符串输入
java复制import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine(); // nextLine()读整行(包含空格)
System.out.println(s);
sc.close();
}
}
关键区别:next()和nextLine()的区别一定要牢记。next()读取到空格或换行符为止,而nextLine()读取整行直到遇到换行符。
6. 图类输入处理
6.1 邻接表表示法
图的常见输入格式是先给出节点数n和边数m,然后是m条边:
java复制import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读n(节点数)和m(边数)
int n = sc.nextInt();
int m = sc.nextInt();
// 构建邻接表
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i <= n; i++) { // 节点从1开始编号
adj.add(new ArrayList<>());
}
// 读m条边
for (int i = 0; i < m; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
adj.get(u).add(v);
adj.get(v).add(u); // 无向图需要添加两次
}
// 输出邻接表(验证用)
for (int i = 1; i <= n; i++) {
System.out.print(i + ": ");
for (int neighbor : adj.get(i)) {
System.out.print(neighbor + " ");
}
System.out.println();
}
sc.close();
}
}
图的构建要点:无向图的每条边需要在邻接表中存储两次(u→v和v→u)。如果是有向图,则只需要存储一次。
7. 实战经验与常见问题
7.1 输入处理中的常见陷阱
-
换行符问题:混合使用nextInt()和nextLine()时,一定要记得在nextInt()后调用一次nextLine()来吸收多余的换行符。
-
输入结束判断:有些题目没有明确给出输入结束标志,这时可以用hasNext()或hasNextInt()来判断:
java复制while (sc.hasNext()) { // 处理输入 } -
大数处理:当输入数值很大时,要注意使用long而不是int,避免溢出。
7.2 性能优化技巧
-
使用BufferedReader:对于大规模输入,Scanner的性能可能不够好,可以改用BufferedReader:
java复制BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = br.readLine(); -
字符串分割优化:频繁使用split()方法可能会影响性能,对于固定格式的输入,可以手动解析字符串。
-
输出缓冲:大量输出时使用StringBuilder来构建输出字符串,减少IO操作次数。
7.3 调试技巧
-
打印中间结果:在处理复杂输入时,可以在每个步骤后打印中间结果,验证是否正确解析。
-
边界测试:特别注意空输入、单个元素输入等边界情况的处理。
-
对比输出:将你的输出与题目示例的输出逐行对比,找出差异。
8. 模板代码的灵活应用
掌握了这些基础模板后,可以根据题目需求进行灵活调整。比如:
-
链表环检测:在构建链表后,可以手动添加一个环来测试算法:
java复制// 构建普通链表 ListNode head = buildList(strs); // 添加环(让尾节点指向第k个节点) int k = 2; // 假设指向第2个节点 ListNode tail = head; while (tail.next != null) tail = tail.next; ListNode target = head; for (int i = 1; i < k; i++) target = target.next; tail.next = target; -
二叉树变种:可以修改构建方法支持不同的输入格式,或者构建二叉搜索树等特殊结构。
-
图的加权处理:对于带权图,可以修改邻接表的结构,存储Pair或自定义的Edge类。
记住,ACM模式下的输入输出处理是算法实现的基础,多练习这些模板代码,在真正面试时就能快速准确地处理各种输入格式,把更多精力放在算法逻辑本身。