ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

在线笔试输入输出全攻略:从EOF循环到高速读入模板

在线笔试输入输出全攻略:从EOF循环到高速读入模板 你刷了三遍 LeetCode Hot 100打开某大厂在线笔试系统结果第一题就卡在输入上样例输入到底是一行还是多行需要循环读多少次读完以后数组怎么切最后输出要不要结尾空格越是习惯在 IDE 里“运行调试一把梭”的人越容易在 OJ 编程题的输入输出上翻车。这里说的输入输出是标准输入 stdout/stdin不是 GPIO、I2C 控制 UART 那种硬件 IO别搞混。大厂在线笔试的测评系统和力扣的“核心代码模式”完全是两套玩法。如果你只刷过函数级题目没有专门练过完整程序的读入读出很容易出现“思路全对、代码全对、但一个点都过不了”的惨案。这篇文章就专门把这层窗户纸捅破把在线笔试里常见的输入输出套路、坑点、性能陷阱和实用模板一次讲透。无论你是准备校招、社招机试还是正在被算法设计与分析期末编程题折磨或者之前在某高校 OJ、华为的 OJ 在线题库里刷题时一脸懵这篇都适合你。1. 大厂机试和力扣最大的差别从“写函数”到“写完整程序”1.1 两种提交模式决定你的代码骨架力扣、牛客的部分专题练习用的是核心代码模式题目已经帮你写好了类和方法签名你只需要往函数里填逻辑比如class Solution: def twoSum(self, nums: List[int], target: int) - List[int]: ...这种模式下你不需要关心数据从哪来也不需要关心结果输出到哪去返回值交给平台处理就行。但大厂在线笔试不是这样。大多数机试用的都是 ACM 模式或者说 IO 模式平台只会提供一个基本空白模板甚至让你自己从main函数开始写。你的程序会被编译成可执行文件平台把输入数据喂给标准输入然后捕获你的标准输出逐字符比对期望答案。这意味着三件事你必须自己负责程序入口和main函数要写对要用标准输入读取所有需要的数据要按照题目要求的标准输出格式把结果打出来。我从 2016 年开始接触各类在线测评系统从信阳师范大学 OJ 到杭州电子科技大学 OJ再到牛客、赛码这种大厂常用平台发现绝大多数人的痛点根本不是算法本身而是“不知道怎么把输入读进来”。算法想出来了代码写了一百行最后卡在读数据上这太憋屈了。1.2 评测系统到底在做什么理解 OJ 的运行机制特别重要。你提交代码后评测系统会经历这些步骤编译你的代码生成可执行文件准备一组或多组测试数据文件把输入文件重定向到标准输入运行你的程序捕获标准输出把输出文件与标准答案文件做比对根据比对结果给你 AC、WA、TLE 或 PE。注意第 4 步评测系统只看你的标准输出 stdout。你打印在控制台上的任何调试信息如果写到了 stdout都会被当成答案的一部分参与比对。这就是为什么很多人本地运行明明没问题一提交就 WA——大概率是调试日志污染了输出。还有一个关键点评测系统不会像 IDE 一样逐行调试你的程序。它运行你的程序等到超时或结束然后看结果。所以你的程序必须能在没有人工干预的情况下自己判断“什么时候读完数据”“什么时候应该结束”。1.3 动手写代码前先确认这三件事我见过太多人拿到题马上写算法结果写了半天才发现连输入格式都没看明白。在 ACM 模式下动手之前必须先回答这三个问题第一数据范围。这道题给的数据是10^4还是10^9这决定了你要用int还是long long决定了用不用快速读入决定了时间复杂度大概要控制到多少。第二输入终止条件。是“第一行给一个整数 T表示有 T 组数据”还是“一直读到 EOF文件结束”这两个完全不一样。把 EOF 循环当成固定组数去写轻则读错数据重则死循环超时。第三输出分隔符。结果之间是用空格还是换行行末允不允许有空格多组数据之间要不要空行精度要求是保留几位小数这些格式问题占了输出类错误的大半。把这三个问题在草稿纸上写出来再动手写 IO 部分能省下大量调试时间。这个习惯我在给郑州轻工业大学 OJ 上刷题的学生做辅导时反复强调他们反馈真的是立竿见影。2. 标准输入的基础读写套路三种必会姿势2.1 Python能短平快也要小心慢Python 在输入输出方面最灵活写法也最多。很多人入门的时候只学过input()这个函数确实简单但它一次只读一行而且底层做了不少额外工作在数据量大的循环里用会明显变慢。我推荐几个固定套路你根据题目场景选场景一全部数据一次性读取按空白切分。import sys def solve(): data sys.stdin.read().split() # 此时 data 是一个 list里面每个元素是字符串 token # 按顺序取用即可 n int(data[0]) arr list(map(int, data[1:1 n])) ...sys.stdin.read()会把整个标准输入读成一个字符串然后调用.split()按任意空白符空格、换行、制表符切分。所有输入数据变成了一个 token 列表用下标或迭代器依次取。这个写法在绝大多数笔试里都够用代码也最好写。场景二逐行读取适合按“行”为单位的输入。import sys for line in sys.stdin: line line.strip() if not line: continue parts line.split() ...这种适合输入格式明确要求“每行一组数据”的题。注意line会包含行尾换行符所以要先.strip()再去处理。场景三追求极致性能时的字节流读取。import sys data sys.stdin.buffer.read().split() # buffer 返回 bytes性能更好如果输入规模是百万行级别sys.stdin.buffer比sys.stdin快一截。它们的用法类似只是元素是字节串需要手动解码或直接用int()转换int()可以接受 bytes 参数。2.2 Ccin 与 scanf 的取舍C 的输入输出是重灾区因为很多人不知道cin默认很慢。先解释原因C 为了兼容 C 的scanf/printf默认让cin/cout与 C 标准 IO 保持同步这导致每次输入输出都有额外开销。想提速就在main第一行写ios::sync_with_stdio(false); cin.tie(nullptr);第一行关闭同步第二行解除cin与cout的绑定。大多数题目这样一加速度基本能赶上scanf/printf。EOF 循环是 C 最常用的多组输入写法int a, b; while (cin a b) { // 处理每组数 }cin 在读到文件末尾时会失败从而退出循环非常适合“没有给定组数、输入到 EOF 结束”的题目。如果要用getline读整行就要注意一个经典坑cin n之后换行符还留在输入缓冲区里这时候直接getline会读到空行。解决办法是加一个cin.ignore();把残留换行吃掉。2.3 JavaScanner 够用但别忘记性能Java 最直观的写法是ScannerScanner sc new Scanner(System.in); int n sc.nextInt(); String s sc.next();但Scanner是出了名的慢在数据量大时会 TLE。我用BufferedReader改造后速度差异经常能达到数倍甚至十几倍BufferedReader br new BufferedReader(new InputStreamReader(System.in)); String line br.readLine(); String[] parts line.split( ); int n Integer.parseInt(parts[0]);如果要读很多整数配一个StringTokenizer或者自己手动解析都行。输出方面大量数据时别用System.out.println一次一次打应该拼到StringBuilder里最后一次性输出。3. 高频笔试输入格式的应对模板从单组到多组、从数字到矩阵大厂笔试题的输入格式看起来千变万化但归纳起来就那几类。每一种都有固定的应对模板这里我直接给出“输入样例 模板代码 为什么这样写”。3.1 没有组数的 EOF 循环这是最常见的格式之一。题目说“多组测试数据每组占一行读到文件尾结束”但不告诉你一共有几组。输入示例1 2 3 4 5 6Python 写法import sys def solve(): data sys.stdin.read().split() # 每次取两个数 for i in range(0, len(data), 2): a int(data[i]) b int(data[i 1]) print(a b) if __name__ __main__: solve()C 写法#include iostream using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int a, b; while (cin a b) { cout a b \n; } return 0; }为什么这样写因为sys.stdin.read().split()把所有空白符全部忽略掉不关心换行还是空格只关心 token 的顺序。cin a b也一样它会自动跳过空白。这样即使输入格式里偶尔多几个空行你的程序也不受影响。3.2 给定组数 T 的多组输入输入示例3 1 2 3 4 5 6第一行 T 表示后面有 T 组数据。Pythonimport sys def solve(): data sys.stdin.read().split() t int(data[0]) idx 1 for _ in range(t): a int(data[idx]); b int(data[idx 1]) idx 2 print(a b) if __name__ __main__: solve()C#include iostream using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin t; while (t--) { int a, b; cin a b; cout a b \n; } return 0; }这里有个常见问题有人会忘记读 T直接把第一个数字当成普通数据。每次都要先确认第一行到底是“组数”还是“真实数据”。3.3 每行包含字符串和数字的混合输入比如题目给出学生姓名和分数需要按行解析Alice 89 Bob 72 Carol 95Pythonimport sys def solve(): for line in sys.stdin: line line.strip() if not line: continue name, score line.split() score int(score) # 处理业务逻辑C#include iostream #include string using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); string name; int score; while (cin name score) { // 处理业务逻辑 } return 0; }这种混合输入的坑主要在于如果用getline整行读再手动切分要记得split出来的是字符串需要挨个转类型。如果直接用cin 它会按空白自动切分反而不容易出错。3.4 二维矩阵与地图类题目矩阵输入的格式也常见比如“第一行给出 n 和 m接下来 n 行每行给出 m 个整数”或者更狠一点的“接下来 n 行每行是一个长度为 m 的 01 字符串中间没有空格”。带空格整数的矩阵3 4 1 2 3 4 5 6 7 8 9 10 11 12Pythonimport sys def solve(): data sys.stdin.read().split() n, m int(data[0]), int(data[1]) idx 2 mat [] for i in range(n): row [int(data[idx j]) for j in range(m)] idx m mat.append(row)不带空格字符串的矩阵3 3 101 010 111这种就不要用split()硬拆了需要按行读然后逐字符处理import sys def solve(): n, m map(int, sys.stdin.readline().split()) mat [] for _ in range(n): line sys.stdin.readline().strip() # 如果 m 较大直接遍历字符 row [int(c) for c in line] mat.append(row)字符串矩阵用split()会把它拆成单个 token101它不是按字符拆开的这个需要特别注意。3.5 多组数据之间的空行怎么处理有些题目的输入描述很烦人“每组之间用一个空行隔开”。很多人看到空行就慌担心自己的程序会把空行当数据处理。好消息是如果你用cin 或者sys.stdin.read().split()空行会被自动忽略因为空行只包含换行符属于空白字符。这类写法天然对空行免疫。但如果你用sys.stdin.readline()逐行处理就需要手动跳过空行while True: line sys.stdin.readline() if not line: break line line.strip() if not line: continue # 正常处理逻辑很简单读到空行就continue。3.6 一个完整的多组输入综合模板最后给一个可以当骨架用的综合模板拿“多组输入每组两个整数输出它们的和直到 EOF”当例子。这种题在杭电 OJ、郑州轻工业大学 OJ 上特别多大厂笔试平台也经常复用类似结构。import sys def solve(): output [] data sys.stdin.read().strip().split() # 注意如果输入可能为空直接返回 if not data: return for i in range(0, len(data), 2): a int(data[i]) b int(data[i 1]) output.append(str(a b)) sys.stdout.write(\n.join(output)) if __name__ __main__: solve()这里我把结果先存到output列表最后一次性用join输出。这样做比在循环里不断print要快得多尤其当输出结果很多时print频繁调用会造成很大性能损耗。4. 输出比输入更容易挂分格式和缓冲区的隐形坑很多同学算法写对了输入也读对了最后还是不能 AC问题就出在输出格式上。输出题的坑比输入更隐蔽因为它不报错只会给你 WA 或者 PEPresentation Error。4.1 行末空格和空行这个老生常谈不少 OJ 在比对答案时会忽略行末空格但也会有很多平台不会那么宽容。你永远不要赌平台会不会忽略它而是要把“输出干净”变成习惯。比如你要输出一个数组[1, 2, 3]常见错误是print( .join(str(x) for x in arr) )结尾多了一个空格。有些平台会因此判 WA。正确写法print( .join(str(x) for x in arr))如果平台要求“每个数字之间用空格分隔最后一个数字后无空格”join天然满足这个要求。多组数据输出之间要不要空行也要分清楚。“每组结果之间空一行”和“每组结果之后都空一行”在前者里最后一组之后通常不能多空行。这种细节只能靠仔细读题没有捷径。4.2 调试日志混进 stdoutWA 到怀疑人生这是我见过最可惜的 WA本地跑样例完全正常一提交就 WA反复检查逻辑都没问题最后发现代码里多了好几行print调试信息。比如# 这是调试日志 print(当前处理到第, i, 行)这行输出会被 OJ 当成答案的一部分跟标准答案比对于是必然不匹配。解决办法有三个提交前全局搜索print、cout、System.out.println检查是否有调试残留调试时把信息输出到stderrPython 用sys.stderr.writeC 用cerrOJ 只比对 stdoutstderr 不会影响判题在本地准备一个“提交专用”版本跟调试版本分开。我用第二条用得最多因为stderr输出的内容本地运行也能看到同时提交后又不会污染答案一举两得。4.3 换行符、精度和缓冲区换行符方面OJ 通常认\nprintf和cout默认用的就是这个。如果你自己在 Windows 环境下写了\r\n大概率会被当成多余字符导致 WA。如果是 Java 的System.out.println它输出的是平台相关的换行在 Linux OJ 上一般是\n问题不大但手动构造字符串拼接时要注意别写成\r\n。浮点数输出是另一个重灾区。printf默认保留 6 位小数但有些题目要求保留 2 位你要用printf(%.2f, ans)。Python 的格式化是f{ans:.2f}Java 是String.format(%.2f, ans)。如果题目有精度要求一定要按它给的规则来多一位少一位都是 WA。还有一个隐蔽的缓冲区问题C 的endl会在输出后刷新缓冲区如果循环里每次输出都用endl数据量大时会非常慢。正确做法是用\n只在最后必要时手动 flush。这在实际机试里真能拉开时间差距。5. 大厂笔试里的性能陷阱当输入规模到百万行5.1 不同读入方式的性能差异真的很大我做过一个粗略测试在 100 万行整数输入下几种常见读入方式的耗时有明显差距语言读入方式相对耗时Pythoninput()逐行极慢容易 TLEPythonsys.stdin.buffer.read()快多数情况够用Ccin默认同步较慢可能 TLECcin关闭同步快大多数题够用Cscanf快JavaScanner很慢大数据量容易 TLEJavaBufferedReader快所以拿到题先看数据范围如果一组输入量超过10^5Python 就不要再考虑input()了直接sys.stdin.buffer.read()。C 就把同步关掉。Java 就用BufferedReader。这不是玄学是实实在在的耗时差异。5.2 读入和输出的内存控制sys.stdin.read().split()虽然方便但它会把整个输入一次性加载到内存然后切成一个巨大的字符串列表。如果输入是 10 万行字符串每种字符串又长内存占用会暴涨。对于大厂笔试平台动辄 256MB 或 512MB 的内存限制有可能会 MLE。这时候就要改用逐行读取处理完一行就扔掉一行不要让所有数据常驻内存。同理输出也要控制如果结果有几万行用join是没问题的但如果结果达到几百万行你就要考虑边算边输出或者分批写避免把内存撑爆。另外要注意Python 里大量使用map(int, list_of_strings)会一次生成很多 int 对象内存开销也不小。如果数据量特别大可以用迭代器的方式逐个取而不是一次性转成列表。5.3 大数溢出和精度陷阱很多算法题的数据范围写着“0 x 10^9”单看没问题但如果你在代码里做了a b两个10^9加起来就是2 x 10^9已经超过int的上限2147483647。如果你用int存储结果直接溢出变成负数然后理所当然 WA。我的经验是在 ACM 模式下凡是没有明确说明数据范围很小的时候一律用long long尤其是加法、乘法、累加结果的场景。Python 没有这个问题因为它的整数是任意精度的但 C 和 Java 的int就是 32 位很危险。浮点数比较也值得注意题目要求输出浮点数时不要用去判断两个浮点数是否相等要用abs(a - b) 1e-9这种方式。输出浮点数时除非题目明确要求“去掉末尾零”之类的特殊处理否则统一用保留指定位数的格式输出。6. 实战复盘我踩过的五次输入输出翻车现场讲太多理论不如看真实车祸现场。这些年我参加过的机试、模拟笔试、给学生答疑时遇到的输入输出问题随便拎出来五个都有代表性。6.1 第一次多打了空行从 AC 变 PE有一道题目要求“每组输出后换行”但我理解成了“每组输出之间用空行隔开”于是在每组结果后额外多输出了一个空行。本地自己看觉得没问题提交后系统提示 PE但分数直接不给。后来我学会了一个笨办法把标准样例的输出下载下来程序输出跑一遍用diff命令对比。眼力再好也有看漏的时候机器比对不会骗人。6.2 第二次调试 print 混进了 stdout那是我第一次在 OJ 上交 Python 题。为了看中间变量我在循环里打印了一个字典的内容交之前忘了删。结果本地运行是正确的在线评测一大堆 WA。后来我把所有调试信息全部改用sys.stderr.write()从此再也没犯过这种低级错误。这里多说一句OJ 平台的反馈有时候会告诉你“wrong answer on test 3”有时候什么都不告诉。如果你本地对了但提交不对第一步就该检查是不是输出里混入了额外字符包括空格、空行、调试输出。6.3 第三次cin 读字符串只读了半截题目描述里说“姓名中间可能有空格”但我用了cin name去读。当输入数据是Zhang San时只读到了Zhang后面的San被当成下一个输入项导致读取错位。改成getline(cin, name)后就好了。这个问题的本质是按空白符切分遇到空格就停getline读一整行。混合数字和字符串的场景里尤其要小心cin n之后换行符残留。加个cin.ignore()或者getline(cin, tmp)把换行吃掉否则getline会直接读到空行。6.4 第四次矩阵题数据量 10^6Scanner 直接 TLE有一道题给了一个 1000 x 1000 的矩阵Java 用Scanner读数据结果算法部分只跑了 200ms输入解析硬生生跑了 2000ms直接超时。换成BufferedReaderStringTokenizer后总耗时降到 400ms。从此我对 Java 的输入写法定了一个规则数据量预估超过 10^5直接用 BufferedReader只有明确知道数据量很小才用 Scanner 图省事。6.5 第五次把 EOF 结束条件当成了固定行数题目描述是“多组测试数据读到文件尾结束”样例输入长这样4 2 3 5 7很多人一看以为“第一行是 n第二行是 n 个数”于是写了只读一次的逻辑。实际上这可能只是第一组后面还会来更多组每一组第一行是元素个数下一行是元素。这种情况下你必须用 EOF 循环包住整个读取逻辑int n; while (cin n) { vectorint arr(n); for (int i 0; i n; i) cin arr[i]; // 处理这一组 }不要把“样例输入恰好长那样”当成“输入真的长那样”。样例只是给你看的后台测试数据可能是几十组。读题时重点看“多组”“EOF”“直到结束”这些词比看样例更可靠。最后分享一个我个人一直沿用的习惯。我在机试前会提前准备好一页通用 IO 模板Python、C、Java 各一份存在本地代码片段里。拿到真题后第一步不急着写算法先标出数据范围、输入终止条件、输出格式这三个信息再套对应的 IO 模板。核心业务逻辑就是在模板骨架里填空而已。这个习惯帮我避免了很多因为紧张写错读入代码的低级失误。也不要指望在线笔试给你调试器你的第一个 AC 就是最诚实的反馈。
返回列表