LeetCode 是核心代码模式,只写函数就行。但笔试(牛客、赛码、各家公司自己的笔试系统)和一部分面试用的是 ACM 模式:输入要自己从标准输入读,输出要自己打印。
算法写对了、结果卡在读入上,是笔试里最冤的丢分方式。这篇按输入格式把常见场景过一遍,C++ 为主,最后附 Java / Python 对照。
先给结论,C++ 记住这四条,绝大多数题都够用:
- 知道个数:直接
cin >>,不用管换行。
- 不知道个数,读到结束:
while (cin >> x)。
- 一行里个数不定:
getline 读整行,再丢给 stringstream 拆。
- 逗号等特殊分隔符:先把分隔符换成空格,再走上一条。
零、模板骨架
1
2
3
4
5
6
7
8
9
10
11
|
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 读入、计算、输出
return 0;
}
|
bits/stdc++.h 一次性包含所有标准库头文件,笔试省事。GCC 能用,MSVC(Visual Studio)没有这个头文件,本地用 VS 编译会报错。
- 开头两行关掉 C / C++ 流同步、解绑
cin 和 cout,数据量大(1e5 以上)时能快很多。不用想,直接写上。
- 关了同步之后,不要再混用
scanf/printf 和 cin/cout,输出顺序会乱。
- 换行用
'\n',别用 endl。endl 每次都会刷新缓冲区,输出多的时候很慢。
一、读取指定个数的数据
最常见的格式:先告诉你有多少个,再给数据。
关键点只有一个:cin >> 会自动跳过所有空白字符(空格、换行、Tab)。所以只要知道个数,数据写在一行还是分成多行,代码都一样,完全不用管换行。
1. 一维数组
1
2
3
4
|
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
|
2. 二维矩阵
1
2
3
4
|
3 4
1 2 3 4
5 6 7 8
9 10 11 12
|
1
2
3
4
5
6
|
int n, m;
cin >> n >> m;
vector<vector<int>> g(n, vector<int>(m));
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> g[i][j];
|
3. 字符网格(迷宫、地图)
1
2
3
4
|
3 4
S..#
.#..
...E
|
每行中间没有空格,直接当字符串读,一次读一行:
1
2
3
4
5
|
int n, m;
cin >> n >> m;
vector<string> g(n);
for (int i = 0; i < n; i++) cin >> g[i];
// 之后用 g[i][j] 访问
|
- 前提是行内没有空格。行内有空格就要用
getline,见第四节。
4. 图的边
第一行 n m 表示 n 个点、m 条边,接下来 m 行每行 u v w:
1
2
3
4
|
4 3
1 2 5
2 3 1
3 4 2
|
1
2
3
4
5
6
7
8
9
|
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> adj(n + 1); // 点编号从 1 开始,直接开 n + 1
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w}); // 无向图才加这一行
}
|
- 点编号从 1 开始就开
n + 1,别自己手动减 1 转成 0 下标,写着写着就漏了。
5. 多组测试数据,先给组数 T
1
2
3
4
5
|
2
3
1 2 3
2
4 5
|
1
2
3
4
5
6
7
8
9
|
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
// 处理这一组,输出答案
}
|
坑:多组数据时,每组用到的容器、计数器要么定义在循环里面,要么每组开头手动清空。定义成全局变量又忘了清,就会第一组对、后面全错。
二、不知道个数,一直读到输入结束
题目只说「输入若干个整数」或者「多组数据,处理到文件末尾」,不告诉你有多少。
1. 读到 EOF
1
2
3
|
vector<int> a;
int x;
while (cin >> x) a.push_back(x);
|
cin >> x 读不到东西时会返回 false,循环自然结束。
2. 多组数据读到 EOF
1
2
3
4
|
int a, b;
while (cin >> a >> b) {
cout << a + b << '\n';
}
|
3. 以特殊值结束
比如「输入 0 0 时结束」:
1
2
3
4
5
|
int a, b;
while (cin >> a >> b) {
if (a == 0 && b == 0) break;
cout << a + b << '\n';
}
|
本地调试时,在控制台里手动敲输入,程序不会自己「读到结束」,会一直卡着等。要手动发 EOF:Windows 下按 Ctrl + Z 再回车,Linux / macOS 下按 Ctrl + D。
更省事的办法是把样例存成文件,重定向进去跑。注意 PowerShell 不支持 <:
1
2
3
4
5
|
# Git Bash / Linux / macOS / cmd
./main < in.txt
# PowerShell
Get-Content in.txt | .\main.exe
|
三、一行数据,个数不定,空格分隔
这是最容易翻车的格式:
第一行是数组,没给个数;第二行是 target。
这时候不能用 while (cin >> x)。cin >> 不认识换行,会把第二行的 10 也当成数组元素读进去。
正确做法:先用 getline 把整行读成字符串,再用 stringstream 从这一行里拆数字。相当于把「读到 EOF」的范围限制在一行里。
1
2
3
4
5
6
7
8
9
10
|
string line;
getline(cin, line);
stringstream ss(line);
vector<int> nums;
int x;
while (ss >> x) nums.push_back(x);
int target;
cin >> target;
|
stringstream 的 >> 和 cin >> 规则一样,按空白切分,多个连续空格也没关系。
多行,每行个数都不定
1
2
3
4
5
6
7
8
9
|
string line;
while (getline(cin, line)) {
stringstream ss(line);
vector<int> row;
int x;
while (ss >> x) row.push_back(x);
if (row.empty()) continue; // 跳过空行(包括只有空格或 \r 的行)
// 处理这一行
}
|
- 用
row.empty() 判断,比用 line.empty() 稳:有些输入是 Windows 换行 \r\n,「空行」里其实还剩一个 \r。
四、特殊符号分隔:逗号、冒号、方括号
或者直接给你 LeetCode 风格的数组:
方法一:把分隔符换成空格(最推荐)
stringstream 只认空白,那就把逗号、方括号都换成空格,问题就回到了第三节。
1
2
3
4
5
6
7
8
9
10
|
string line;
getline(cin, line);
for (char& c : line) {
if (c == ',' || c == '[' || c == ']') c = ' ';
}
stringstream ss(line);
vector<int> nums;
int x;
while (ss >> x) nums.push_back(x);
|
再狠一点:只要不是数字和负号,统统换成空格。逗号、分号、方括号、x = 1, y = 2 这种格式,都能把数字抠出来:
1
2
3
|
for (char& c : line) {
if ((c < '0' || c > '9') && c != '-') c = ' ';
}
|
这招有几个前提:
- 有小数要把
. 也留下,读成 double。
- 本身当分隔符用的(比如 2024-01-05)不行,会被当成负号。这种用方法三。
- 字母和数字混在一起的(比如
x1 = 3)也不行,会把 1 也抠出来。
方法二:getline 指定分隔符切分
getline 的第三个参数可以指定分隔符,默认是 '\n'。拿它在 stringstream 上切:
1
2
3
4
5
6
7
8
9
|
string line;
getline(cin, line);
stringstream ss(line);
string token;
vector<int> nums;
while (getline(ss, token, ',')) {
nums.push_back(stoi(token));
}
|
比方法一麻烦,只在这两种情况下用:
- 切出来的是字符串,而且字符串里可能带空格,比如
Tom Smith,Alice,Bob。
- 需要保留空字段,比如
a,,b 要切出 3 段,中间是空串。
坑:
stoi 超出 int 范围会抛异常,数据大就用 stoll。
- 空字段
stoi("") 也会抛异常,转数字之前先判空。
- 行尾带
\r 的话,最后一个 token 也会带着 \r。转数字没影响,但拿去做字符串比较就对不上了。
方法三:格式固定时,用一个 char 把分隔符读掉
格式固定(时间 12:30、日期 2024-01-05),或者题目给了个数,最省事的是用一个 char 变量把分隔符「吃掉」:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
char ch;
// 12:30
int h, m;
cin >> h >> ch >> m;
// 2024-01-05
int y, mo, d;
cin >> y >> ch >> mo >> ch >> d;
// 已知 n 个数,逗号分隔:1,2,3,4,5
vector<int> a(n);
for (int i = 0; i < n; i++) {
if (i > 0) cin >> ch; // 第一个数前面没有逗号
cin >> a[i];
}
|
cin >> ch 同样会跳过空白,所以 1, 2, 3 这种逗号后面带空格的也能读。
- 用
scanf("%d:%d", &h, &m) 也行,但前面说了,关了同步就别和 cin 混用。
五、字符串:cin >> 和 getline 的区别与混用
两种读法的区别:
cin >> s:读一个「单词」,碰到空白就停。
getline(cin, s):读一整行,包括中间的空格,不包括末尾的 \n。
混用时有个经典的坑:
1
2
3
4
|
3
hello world
foo bar
baz
|
第一行是行数,后面每行是一个可能带空格的字符串。直接这么写会出错:
1
2
3
4
|
int n;
cin >> n;
string line;
getline(cin, line); // 读到的是空字符串!
|
坑:cin >> n 读完 3 就停了,3 后面那个换行符还留在缓冲区里。紧接着的 getline 一上来就碰到换行,直接结束,读到一个空行。
解决办法是在 getline 之前把空白跳过去:
1
2
3
4
5
6
|
int n;
cin >> n;
vector<string> lines(n);
for (int i = 0; i < n; i++) {
getline(cin >> ws, lines[i]); // ws:跳过开头所有空白,包括残留的换行
}
|
- 为什么不用网上常见的
cin.ignore()?它默认只跳过一个字符。如果 3 后面还跟了个空格或者 \r,照样出错。cin >> ws 不管有几个都吃掉,更稳。
ws 的代价是行首空格、空行也会被跳过。绝大多数题无所谓。真遇到在意行首空格的题,就在 cin >> n 之后单独 getline 一次,把这一行剩下的部分读掉扔了。
六、顺手说一下输出
- 空格分隔输出,行末不留多余空格。大部分评测会忽略行末空格,但不是全部,没必要赌:
1
2
3
4
5
|
for (int i = 0; i < n; i++) {
if (i > 0) cout << ' ';
cout << a[i];
}
cout << '\n';
|
1
|
cout << fixed << setprecision(2) << ans << '\n'; // 保留 2 位
|
- 数据范围超过 int(约 2.1e9),或者要把一堆 1e9 级别的数加起来,读入和答案都用
long long。
七、最通用的写法(复制即用)
把前面最常用的两个操作封装一下,笔试开场直接贴:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#include <bits/stdc++.h>
using namespace std;
// 把一行里的整数全抠出来:兼容空格、逗号、[1,2,3] 等格式
vector<long long> parseNums(string line) {
for (char& c : line) {
if ((c < '0' || c > '9') && c != '-') c = ' ';
}
stringstream ss(line);
vector<long long> res;
long long x;
while (ss >> x) res.push_back(x);
return res;
}
// 按指定分隔符切分成字符串,中间的空字段会保留
vector<string> split(const string& s, char delim) {
vector<string> res;
stringstream ss(s);
string token;
while (getline(ss, token, delim)) res.push_back(token);
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string line;
getline(cin, line);
vector<long long> nums = parseNums(line);
// ...
return 0;
}
|
还有一个兜底办法:题面写得含糊、样例和描述对不上、拿不准格式的时候,先把所有行原样读进来,再慢慢解析:
1
2
3
4
|
vector<string> lines;
string line;
while (getline(cin, line)) lines.push_back(line);
// lines.size() 就是总行数,lines[i] 是第 i 行
|
先跑一遍样例,把读进来的东西打印出来核对,比对着题面猜强得多。
八、Java / Python 对照
Java
Scanner 最好上手,和 C++ 的写法基本一一对应:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 知道个数
int n = sc.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) a[i] = sc.nextInt();
// 读整行:和 C++ 同一个坑,nextInt 之后要先把残留的换行吃掉
sc.nextLine();
String line = sc.nextLine().trim();
String[] bySpace = line.split("\\s+"); // 空格分隔,连续多个空格也行
String[] byComma = line.split(","); // 逗号分隔
// 万能版:非数字、非负号全换成空格再切,和 C++ 的 parseNums 一个思路
String[] tokens = line.replaceAll("[^0-9-]+", " ").trim().split("\\s+");
// 读到 EOF
while (sc.hasNextInt()) {
int x = sc.nextInt();
}
}
}
|
- 类名必须是
Main,牛客之类的平台不认别的名字。
- 空字符串
split 之后会得到一个 [""],直接 Integer.parseInt 会报错,空行要先判断。
Scanner 慢,数据量到 1e5 以上可能超时,换成 BufferedReader + StringTokenizer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine().trim());
StringTokenizer st = new StringTokenizer(br.readLine());
int[] a = new int[n];
for (int i = 0; i < n; i++) a[i] = Integer.parseInt(st.nextToken());
// 读到 EOF
String line;
while ((line = br.readLine()) != null) {
StringTokenizer t = new StringTokenizer(line, " ,"); // 第二个参数:空格和逗号都算分隔符
while (t.hasMoreTokens()) {
int x = Integer.parseInt(t.nextToken());
}
}
}
}
|
- 这种写法是按行读的,要求 n 个数都在同一行。换行位置不固定的题,还是用
Scanner 省心。
- 输出多的时候别在循环里
System.out.println,先拼到 StringBuilder 里,最后一次性打印。
Python
Python 写输入最短:
1
2
3
|
n = int(input())
a = list(map(int, input().split())) # 一行空格分隔,个数不定也一样
b = list(map(int, input().split(','))) # 逗号分隔
|
最通用的是一把读完所有 token,再按顺序取,完全不用管换行:
1
2
3
4
5
6
|
import sys
data = sys.stdin.read().split()
it = iter(data)
n = int(next(it))
a = [int(next(it)) for _ in range(n)]
|
格式乱七八糟的([1,2,3]、x = 1, y = 2),直接用正则抠数字:
1
2
3
|
import re
nums = list(map(int, re.findall(r'-?\d+', input())))
|
input() 慢,数据量大就在开头加 input = sys.stdin.readline。注意它返回的行带着 \n:int() 和 split() 不受影响,但读字符串要记得 .strip()。
小结
我的建议:
- C++ 记四个写法就够了:
- 知道个数 →
cin >>,不用管换行。
- 读到结束 →
while (cin >> x)。
- 一行个数不定 →
getline + stringstream。
- 特殊分隔符 → 先换成空格,再
stringstream。只有要切字符串、要保留空字段时才用 getline(ss, token, ',')。
- 两个习惯永远带上:开头写
ios::sync_with_stdio(false); cin.tie(nullptr);;cin >> 后面接 getline 之前先 cin >> ws。
- Java 先用
Scanner,类名写 Main;数据量大再换 BufferedReader。
- Python 最省心的是
sys.stdin.read().split() 一把读完,再按顺序取。
- 拿不准格式,就先把所有行读进来打印看看,别对着题面猜。