ARTICLE DETAIL

资讯详情

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

Python3 ACM模式输入输出全攻略:从input到sys.stdin

Python3 ACM模式输入输出全攻略:从input到sys.stdin 我见过太多刷题党在力扣上滚瓜烂熟一到牛客网笔试就卡在输入上。题目读懂了思路也有了代码写了一半结果程序连数据都读不进来白扣一堆分。Python3的ACM模式输入输出说白了就是一套“和评测系统打交道”的基本功题目把测试数据放到标准输入里你需要自己解析题目要求标准输出你需要把结果按指定格式打印。这篇文章就把我从牛客网刷题到准备华为机试期间积累的输入输出模板、常见场景拆解和避坑经验一次性讲清楚适合刚开始准备机试、或者被输入输出坑过几次的读者。ACM模式的关键不在算法复杂度而在“准确地读取数据”和“规范地输出结果”。很多刷题经验贴都默认你会处理输入但实际考场上读行、切分、转类型、处理EOF这些事任何一个环节出问题整道题就废了。我尽量把每类场景都给出可以直接抄的模板再解释为什么这么写争取让你看完就能上手。1. ACM模式到底是什么和力扣的核心代码模式差在哪1.1 在线笔试为什么偏爱ACM模式先看两种模式的区别。力扣这类平台喜欢“核心代码模式”系统帮你把输入解析成函数参数你只需要实现一个函数并返回结果底层测试用例自动帮你组装好。这种模式很友好但放到企业笔试里就不太够用了。牛客网、华为机试以及大部分国企银行的机考采用的往往是ACM模式。它要求考生自己处理标准输入输出stdin/stdout题目的所有数据都由评测系统通过标准输入喂给程序程序则把答案输出到标准输出。这种模式更接近真实工程里“写一个脚本处理数据流”的场景也能顺便考察考生对IO、边界条件、异常情况处理的基本功。1.2 核心代码模式与ACM模式的对比我用一张表把两者的差异列清楚这样你就能直观理解为什么刷惯力扣的人第一次做牛客题会懵。对比维度核心代码模式力扣ACM模式牛客/华为机试输入数据已作为函数参数传入需要自己从标准输入解析输出结果通过return返回需要自己用print或sys.stdout写入标准输出示例代码只需要写函数体需要包含完整的数据读取与主流程逻辑典型场景LeetCode、部分周赛牛客网笔试、华为机试、蓝桥杯、ACM练习平台题目提示题目会告诉你函数签名题目只描述输入输出格式一切靠你自己核心代码模式好比你在餐厅后厨只负责炒菜食材配菜全部帮你码好了ACM模式则要求你自己洗菜、切菜、配菜、炒菜、装盘最后还得自己端到出餐口。1.3 评测系统是怎么运行你的代码的在线评测系统OJ的流程大致是读取你的源码用指定的编译器和运行环境跑起来然后把预设好的输入文件重定向到程序的标准输入。你的程序从标准输入读数据最后系统拿到全部标准输出再和答案文件做比较。这里有个很关键的点评测系统对比的是“标准输出”的文件内容。如果你在代码里多写了一句print(请输入两个数)这道题大概率就判错了因为那行提示语也会被当成输出的一部分。所以ACM模式下所有提示性文字都不要print出来。另外很多OJ在比对输出时会忽略行尾多余空格和文件末尾的换行符但并不是所有OJ都这样。最稳妥的做法是严格按照题目给出的输出格式来行尾不要有多余空格最后一行该有换行就保留不该有就不加。2. Python3 输入基本功从input()到sys.stdin2.1 input()与sys.stdin.readline()什么时候选哪个Python3里最简单的输入方式当然是input()。它的一次调用会从标准输入读取一整行并去掉末尾的换行符返回字符串。日常刷题、数据量不大的场景完全够用。但如果你要处理大量数据比如一次输入几十万行input()的性能就不太行了。原因在于input()底层做了一些额外处理包括解码、去换行等虽然单次开销很小但循环次数一多就会有明显差距。这时候建议用sys.stdin.readline()import sys line sys.stdin.readline() # 读取一行包含末尾换行符 line sys.stdin.readline().strip() # 去掉两端空白通常是换行和空格要注意sys.stdin.readline()通常会把末尾的\n也读进来所以一般配合strip()去掉。而input()已经帮你去掉了末尾换行这是两者最直观的区别。我实测过在循环读取1e6行级别数据时sys.stdin.readline()比input()能快接近一倍。笔试如果碰到大数据量题目这个差异会直接影响你是否超时。2.2 读取多个测试用例处理EOF是关键ACM模式里有一个高频场景题目没有告诉你几组数据只说“输入包含多组测试用例每组占一行处理到文件结束为止”。这种题在牛客网和华为机试里太常见了。标准写法是用for line in sys.stdin循环读取等文件读完了循环自然结束import sys for line in sys.stdin: line line.strip() if not line: continue a, b map(int, line.split()) print(a b)这里line.strip()是为了去掉换行符和行首行尾多余空格if not line: continue是为了跳过空行。有些输入文件末尾会多一个空行不跳过的话会干扰后续解析。如果你更习惯input()也可以这样写while True: try: line input() except EOFError: break if not line.strip(): continue a, b map(int, line.split()) print(a b)核心原理是input()在读到文件末尾时会抛出EOFError异常我们用try/except捕获它然后break退出循环。这两种写法效果完全一样选择顺手的那套即可。2.3 一次读完再解析sys.stdin.read().split()还有一种更“暴力”但很好用的方式把整个标准输入一次性读进来然后统一按空白字符切分。import sys data sys.stdin.read().split()这一步会把所有输入内容里的空格、换行都当作分隔符得到一个字符串列表。比如输入是3 1 2 3经过sys.stdin.read().split()之后data [3, 1, 2, 3]。这种方式的优点是代码简洁、解析逻辑统一尤其适合“第一行是n后面n个数”这种结构性比较强的输入。我个人的习惯是只要能确定输入格式就优先用sys.stdin.read().split()把所有数据切成词法单元然后用索引或迭代器依次取。这样最不容易在“多一个空行”“多一个空格”这类细节上翻车。import sys data sys.stdin.read().split() if not data: exit() it iter(data) n int(next(it)) nums [int(next(it)) for _ in range(n)] print(sum(nums))注意data为空的情况一定要处理否则直接用data[0]会报IndexError。在线评测虽然很少给空输入但本地自测时很容易遇到。如果你追求极致性能还可以用sys.stdin.buffer.read().split()它返回的是字节串列表而不是字符串列表读取速度会更快因为省去了字符串解码的部分。比如import sys data sys.stdin.buffer.read().split() n int(data[0])字节串可以直接传给int()int(b12)结果是12不必手动decode。大数据量的输入场景我推荐优先写成buffer读法。3. 高频输入场景模板牛客和华为机试常见套路3.1 第一行是整数n后面跟着n个整数这个场景最常见比如“第一行一个整数n第二行n个整数求排序后的结果”。可以用read().split()一次性解析import sys data sys.stdin.read().split() n int(data[0]) nums [int(x) for x in data[1:1 n]] nums.sort() print( .join(map(str, nums)))注意data[1:1n]切片很好用正常情况下第1个是整数个数后面n个是数组元素。如果输入里有多余的空格或换行split()会全部处理掉不需要额外操心。3.2 第一行是T表示有T组测试数据这类输入最常见的结构是2 1 2 3 4第一行的T表示后面还有T行数据。解析模板如下import sys def main(): data sys.stdin.read().split() if not data: return t int(data[0]) idx 1 results [] for _ in range(t): a int(data[idx]) b int(data[idx 1]) idx 2 results.append(str(a b)) sys.stdout.write(\n.join(results)) if __name__ __main__: main()用idx做指针依次读取比一行行input()更稳定因为不管输入是换行分隔还是空格分隔最终都会被split()切成统一的结构。当然如果每组数据恰好占一行直接for _ in range(t): a, b map(int, input().split())也行。但一旦题目说每组数据可能跨行或者某行有多个数指针法就更有优势了。3.3 多行输入直到文件结束EOF已经在2.2节写过了这里再给出一个带统计逻辑的模板便于直接改import sys total_sum 0 for line in sys.stdin: line line.strip() if not line: continue numbers list(map(int, line.split())) total_sum sum(numbers) print(total_sum)这个模式特别适合处理“每行若干个数求总和/最大值/频率”这类题。只要循环能正常走到文件末尾程序就会自动结束不需要额外的终止标记。3.4 二维矩阵输入网格题的地基网格类题目在机试里非常常见比如DFS、BFS、动态规划。输入通常是第一行n m表示n行m列后面跟着n行数据。数字矩阵的读法import sys def main(): data sys.stdin.read().split() if not data: return n int(data[0]) m int(data[1]) idx 2 grid [] for _ in range(n): row [] for _ in range(m): row.append(int(data[idx])) idx 1 grid.append(row) print(grid) if __name__ __main__: main()如果矩阵里的元素是字符比如#、.、S、E就可以用更简短的按行读取import sys n, m map(int, sys.stdin.readline().split()) grid [list(sys.stdin.readline().strip()) for _ in range(n)]这种写法要求每一行恰好是m个字符且没有空格。如果字符之间有空格就得用.split()再做一次。3.5 字符串和混合类型输入的解析很多题目会混着数字和字符串比如“输入一个姓名和一个分数”像这样Alice 90 Bob 85最简单的解析方式就是split()它会按空白切分得到[Alice, 90]然后分别处理import sys records [] for line in sys.stdin: parts line.split() if len(parts) ! 2: continue name parts[0] score int(parts[1]) records.append((name, score))如果字符串内部可能有空格比如姓名是“Zhang San”那输入格式通常会明确告诉你用逗号或分号分隔这时就用指定分隔符切分parts line.strip().split(,) name parts[0].strip() score int(parts[1].strip())总之不确定分隔符的时候先用split()默认按空白切有明确逗号、分号、竖线等情况就传参进去切。最怕的是混合字符和数字时没有统一处理导致int()报ValueError。3.6 链表和二叉树的ACM模式输入牛客网和华为机试的某些题目会给出类似“给定一个数组构造一颗二叉树/链表”的输入。这种题目本质上还是在考数据结构的基本构造能力。以链表为例5 1 2 3 4 5先读取n和数组再构造链表class ListNode: def __init__(self, val0, nextNone): self.val val self.next next def build_linked_list(values): dummy ListNode() cur dummy for v in values: cur.next ListNode(v) cur cur.next return dummy.next def main(): import sys data sys.stdin.read().split() if not data: return n int(data[0]) nums list(map(int, data[1:1 n])) head build_linked_list(nums) # 处理逻辑... if __name__ __main__: main()构造二叉树的思路也类似先用数组存节点值再用层序或前序顺序把节点连接起来。这类题目真正消耗时间的往往是输入解析只要你把“读数组→构造结构”这段练熟后面就能把精力放在算法本身。4. 输出格式与性能优化输出不对全盘皆输4.1 print的常用控制技巧print()默认输出一行且行尾带换行符。多个值输出时默认用空格分隔比如print(a, b)会输出a b。这些默认行为在大多数场合够用但需要精确控制格式时就得用sep和end参数print(a, b, sep,) # 输出 a,b print(x, end ) # 输出后不换行而是空格 print() # 手动换行输出一个整数列表用空格分隔最优雅的写法是print(*nums)等价于把列表每个元素展开作为print的参数。但如果列表比较大或者元素是字符串我更推荐join构造大字符串再输出print( .join(map(str, nums)))两种都行但join方式在数据量大时性能更稳。4.2 不同输出格式的模板通用规则先把结果存成一个字符串列表最后一次性输出。这样代码清晰也方便调试。一行一个结果out_lines [] for x in results: out_lines.append(str(x)) sys.stdout.write(\n.join(out_lines))每个结果本身又是一个列表需要按行内空格分隔out_lines [] for row in matrix: out_lines.append( .join(map(str, row))) sys.stdout.write(\n.join(out_lines))需要保留浮点数精度时print(f{value:.2f})注意格式化输出时f{value:.2f}会自动做四舍五入OJ的浮点数对拍一般不会抠到最后一个误差位所以直接用格式化输出即可。4.3 大数据量输出别让print拖慢你的程序很多题目输入大输出也大。如果结果有几万行你每一行都用print()程序的运行时间就会显著上升因为每一次print()都是一次标准输出写入操作系统调用开销累积起来非常可观。正确的做法是把所有输出拼成一个大的字符串最后用一次sys.stdout.write()写完import sys out \n.join(map(str, results)) sys.stdout.write(out)如果输出的结果是迭代器生成也可以用io.StringIO缓冲from io import StringIO buf StringIO() for x in results: buf.write(str(x)) buf.write(\n) sys.stdout.write(buf.getvalue())我自己写题的习惯是能先存结果就存结果最后统一输出。这样既避免频繁IO也能在自测时先把结果打印出来检查非常方便。4.4 调试输出和提交输出分离做题时经常需要打印中间变量调试。但提交时如果忘记删掉调试信息就会导致答案错误。我的习惯是调试信息全部往sys.stderr里写或者使用注释标记import sys # DEBUG True DEBUG False if DEBUG: print(debug: current idx , idx, filesys.stderr)sys.stderr的输出不会污染标准输出OJ对比时也不会读取它所以哪怕忘记删除也不会影响判题。不过这终究是习惯问题建议提交前还是检查一遍。5. 从真题场景拆解四个高频实战案例5.1 多组整数求和牛客经典例题题目描述很常见多组输入每行两个整数a和b每行输出ab的结果处理到文件结束。import sys for line in sys.stdin: line line.strip() if not line: continue a, b map(int, line.split()) print(a b)这道题本身没有算法难度最大的坑就是“多组输入到EOF”。如果你只调用一次input()程序读完第一行就结束了后面的用例全都不会处理OJ的结果自然不对。我实际笔试时遇到过一种情况输入文件末尾带了几个空行。如果不加if not line: continue空行会触发ValueError因为map(int, .split())拿不到两个数。所以一定要养成“读完先strip再判断空行”的习惯。5.2 数组排序并输出华为机试高频基础题题目可能是第一行输入整数n第二行输入n个整数从小到大排序后输出空格分隔。import sys def main(): data sys.stdin.read().split() if not data: return n int(data[0]) nums list(map(int, data[1:1 n])) nums.sort() sys.stdout.write( .join(map(str, nums))) if __name__ __main__: main()这种题最适合用read().split()一次读完再解析因为字母顺序非常固定。只需要注意data[1:1 n]这个切片边界别把多余的空行或最后一个数字漏掉。如果你不确定n和实际数组长度是否一致可以用nums list(map(int, data[1:]))全收下来再用nums[:n]截断。灵活处理边界比死板地卡索引更不容易出错。5.3 字符串统计统计每个单词出现次数题目输入若干行英文文本统计每个单词出现的次数按出现次数降序、字典序升序输出。import sys counter {} for line in sys.stdin: for word in line.split(): word word.strip(.,!?;:\()) if not word: continue counter[word] counter.get(word, 0) 1 for word, cnt in sorted(counter.items(), keylambda x: (-x[1], x[0])): print(f{word} {cnt})这里有两个ACM输入输出相关的小点一是用line.split()天然按空白切分不需要手动处理换行符二是最后排序输出的格式关键词要和计数之间有空格多行结果各占一行。如果不小心用print(word, cnt)默认输出中间是一个空格也符合要求这题没有问题。5.4 二维网格查找目标值矩阵输入实战看一个稍微综合一点的例子读取一个n行m列的矩阵判断矩阵里是否存在目标数字target存在输出YES否则输出NO。import sys def main(): data sys.stdin.buffer.read().split() if not data: return n int(data[0]) m int(data[1]) target int(data[2]) idx 3 matrix [] for _ in range(n): row [] for _ in range(m): row.append(int(data[idx])) idx 1 matrix.append(row) for row in matrix: if target in row: print(YES) return print(NO) if __name__ __main__: main()这个案例的关键在于把矩阵的数据从一维的data列表里按行重新组装成二维结构。理解这个“指针移动”逻辑以后读任何固定格式的多维数据都不会心慌。5.5 边界中间的“只读第一行”技巧有些题只读取第一行就能计算比如“输入一个由空格分隔的整数数组输出最大值”。这时候你甚至不需要循环import sys line sys.stdin.readline() nums list(map(int, line.split())) print(max(nums))千万别写成sys.stdin.read()配合循环去逐行处理那样代码复杂度反而高了。读多少、怎么读永远以题目输入格式为准能用一行解决就用一行。6. 常见问题排查与华为机试备考心得6.1 最常翻车的四个输入输出错误第一个错误输出里混入了提示文字。这个前面强调过最常见的死法就是print(请输入n:)之后读输入OJ会把那行提示当成答案的一部分直接判错。第二个错误while True: input()没有捕获EOFError。本地测试时你手动敲了CtrlD或CtrlZ结束输入程序可能正常退出但OJ上输入文件自动结束input()就抛异常了。必须用for line in sys.stdin或try/except EOFError处理。第三个错误split()之后忘了转类型。输入里都是字符串如果忘记int()就做算术运算会直接TypeError。第四个错误字符串和整数混在一个列表里用索引取值时搞错顺序。比如先读了n又读数组结果数组没读完就进行了下一步。解法是尽量用sys.stdin.read().split()统一切分然后用一个idx指针顺序读取。6.2 问题排查速查表我在实际刷题过程中把常见问题整理成了一张自查表每次遇到问题就按表排查效率很高。现象可能原因解决方案本地运行正常OJ全判错输出多写了提示语、输出格式不一致检查所有print确保只输出答案程序一直等待输入不结束while循环里没有正确处理EOF改用for line in sys.stdin或捕获EOFErrorValueError: invalid literal for int()空行或带逗号等分隔符没有被处理先strip()再确认分隔符必要时用split(,)答案超时大量输入使用input()、输出大量使用print()改成sys.stdin.buffer.read()和一次性writeIndexError: list index out of range读取的数据比预期少索引越界先打印data看看实际内容再调整切片范围RecursionError递归深度超过Python默认限制在代码开头设置sys.setrecursionlimit(1000000)多输出了空行某行读取后strip再print时多出一个换行检查空行过滤逻辑确认输出拼接方式6.3 从牛客到华为机试的备考建议牛客网是练ACM模式输入输出的好地方。我的建议是平时刷题时不要只在“核心代码模式”里练尽量把题目切换到“ACM模式”或者直接在牛客的在线自测里手写完整代码。只有平时练过读stdin、写stdout考场上才不会慌。备考华为机试也一样。华为机试在线编程题基本是ACM模式系统会跑多个测试用例输入规模可能不小。我自己的准备策略是第一先背熟统的模板。不管什么题代码开头固定写import sys def main(): data sys.stdin.buffer.read().split() if not data: return # 解析逻辑... if __name__ __main__: main()这个框架能覆盖绝大多数“固定格式输入”的题目。先把模板写好再把算法逻辑填进去能省下很多临场思考时间。第二善用本地文件重定向测试。笔试环境一般都能用本地IDE提前准备好输入文件用一行命令运行代码python3 main.py input.txt output.txt这样可以反复验证输入输出格式不用每次手动复制粘贴。第三注意华为机试的时间限制。Python在华为机试里通常可用但大数据量下要注意性能。避免在循环里频繁print避免用字符串拼接大量结果尽量用列表存结果然后join。第四也是最容易被忽略的一点养成“提交前脑内跑一遍样例”的习惯。我每次提交前都会对着题目给的样例把数据在脑子里或者草稿纸上走一遍确认读取顺序没有错位。这个习惯帮我拦住了至少三次因为读错顺序导致的失误。我从一开始在牛客被输入输出卡得怀疑人生到现在总结出这一整套模板最大的体会是ACM模式的输入输出并没有多高深它就是把“和数据打交道”这件事摆到了明面上。数据怎么读、结果怎么写不靠临场发挥而靠平时积累的模板和自查清单。刷题的时候多花点时间把基础IO练到不需要动脑思考到了真正的笔试环境你就能把精力全部放在算法本身上。
返回列表