ARTICLE DETAIL

资讯详情

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

Python3 ACM模式输入输出全攻略:从input到sys.stdin,牛客华为机试必看

Python3 ACM模式输入输出全攻略:从input到sys.stdin,牛客华为机试必看 如果你正在用 Python3 刷牛客网或者准备华为机试八成会遇到一个魔幻场景本地 IDE 里跑得好好的代码一贴进在线编辑器提交直接 0 分。排查半天发现问题根本不在算法而是 ACM 模式下的输入输出没处理好。市面上的题解大多只贴核心函数可笔试平台要的是完整程序自己从标准输入读数据再把结果输出到标准输出。这篇文章就把这层窗户纸捅破从最基础的 input() 到 sys.stdin 的性能优化再到牛客网和华为机试里的高频输入场景模板一次性讲透。适合准备求职机试、算法竞赛以及刚转用 Python3 刷题的同学。1. ACM模式到底在考什么1.1 核心代码模式和ACM模式的本质区别先讲清楚一个很多人懵了很久的概念差。核心代码模式国内一般叫“函数模式”力扣那种是平台把输入解析好把数据当成参数传给你你只需要实现某个类的某个方法最后 return 结果。而 ACM 模式正好反过来平台给你一堆标准输入文本你要自己负责把数据从 stdin 里读出来解析成想要的变量计算完之后再把结果通过 stdout 打印出去由评测系统比对输出文件。听起来很简单但两种模式对人的要求完全不同。核心代码模式就像去食堂窗口点菜你告诉师傅要什么菜师傅把盛好的餐盘递给你ACM 模式则是自助餐菜都在台子上摆着标准输入你得自己拿盘子申请变量、自己夹菜解析数据、自己决定怎么吃算法逻辑最后还得把盘子放到指定位置格式化输出。很多习惯了函数模式的人一换到 ACM 模式就手足无措不是不会写算法是根本不知道数据该怎么读进来。我整理了一个对比表方便直观感受对比维度核心代码模式ACM模式输入来源函数参数传入标准输入 stdin输出方式return 返回值标准输出 stdout代码结构实现指定函数/类完整可运行程序代表平台力扣、部分赛码网题目牛客网、华为机试、竞赛主要考察算法思维算法思维 完整工程处理能力这里要特别提醒一句笔试环境下很多公司笔试系统用的是牛客或赛码这类平台它们默认就是 ACM 模式。你光在力扣上刷题刷得再熟如果从没练过完整读入输出上考场大概率要吃亏。1.2 为什么牛客网和华为机试都爱用ACM模式其实这不是平台刻意刁难人而是有实际原因的。第一批原因在于评测机制ACM 模式下平台只需要生成输入文件运行你的程序然后把你的 stdout 和预先算好的标准答案文件做字符级比对整个系统实现非常统一不依赖任何语言绑定。反观核心代码模式平台就得为每种语言写一套适配层把输入数据序列化成参数再塞进你的函数工程复杂度高一截。第二个原因是防止作弊和增强随机性。ACM 模式可以给不同考生生成不同的测试数据输入规模、数据顺序都能动态变。比如同一道排序题你的程序必须能处理不同长度的输入行不能写死。这更接近真实工作中处理外部数据流的状态——你写出来的脚本要能扛得住千奇百怪的输入格式而不是只在固定参数上能用。第三个原因可能更现实很多机试系统是直接从算法竞赛平台改过来的底子就是 ACM 风格。牛客网的在线编程模块、华为 OD 机试系统从小范围到大厂招聘基本都继承了这套“标准输入输出 评测机比对”的玩法。你与其抱怨不如把 IO 这块练成肌肉记忆省下考试时的脑力去思考真正的算法问题。1.3 机试读题的第一件事看输入输出描述千万不要一拿到题目就埋头写代码。ACM 模式题目通常会在“输入描述”里给出明确格式比如“第一行包含一个整数 T表示测试数据的组数”“接下来 T 行每行包含两个整数 a 和 b”。在读题阶段你要先判断出这属于哪一类输入场景然后直接套对应的模板而不是现场想。有一次我带一个学弟准备机试他卡在一道很简单的题上半小时原因是题目写“输入包含多行每行包含两个整数”他没意识到这是 EOF 读到底的模式非要去数一共有几组结果用 int(input()) 反复读一提交就崩。所以我会在下一节把所有常见场景的模板整理出来先背熟再谈其他。2. 输入基础从 input() 到 sys.stdin2.1 input() 的常见用法和暗坑Python3 里最无脑的读入方式就是 input()。它一次读取一行文本自动去掉末尾的换行符返回一个字符串。需要注意它不会自动去掉行首尾的空格如果你读的是「一行里有多个空格分隔的数字」必须手动 split。绝大多数题目里输入文本规规矩矩input() 够用但它有几个暗坑值得先说。第一个坑读到文件末尾时会抛 EOFError。比如题目要求“处理到文件结束”如果你用 while True 死循环 input()最后就会崩溃。第二个坑input() 默认读整行如果一行里既有整数又有字符串不要直接 int(input())要整体读进来再拆。第三个坑它不是最快的读入方式一旦数据量到几十万行级别性能就会有明显差距。我平时最推荐的三种 input() 写法# 读一个整数 n int(input()) # 读一行空格分隔的整数 nums list(map(int, input().split())) # 读一个整数和一个字符串再读一行字符串 k int(input()) s input().strip()第三行里为什么加 .strip()因为 input() 虽然去掉末尾换行但如果这一行有行尾空格或者你想统一去除首尾空白strip() 是更稳妥的。不过也要注意如果题目明确告诉你某个字符串里的空格是有意义的那你不能随便 strip()只能手动按字符处理。2.2 sys.stdin机试提速的关键一步当你的代码在大数据上超时先别急着优化算法先看看读入是不是太慢。很多人不知道Python3 的 input() 是有额外开销的每次调用都要做编码解码、处理提示字符串等操作而 sys.stdin 本身是一个缓冲流对象直接调用它的 readline() 或 read() 效率要高不少。举个例子。有次帮人排查一道数组统计题数据量接近 100 万行用 input() 在本地跑要 4 秒多换成 sys.stdin 直接读不到 1 秒就完成了。这种差距在算法题里是非常致命的因为很多机试限时 1 到 2 秒读入慢基本等于死刑。最常用的两套 sys.stdin 写法import sys # 逐行读 for line in sys.stdin: line line.strip() if not line: continue # 这一行自己按需要解析 # 全量读按空白字符切分 data sys.stdin.read().split()第二种写法尤其适合“第一行给 n第二行给 n 个数”这种固定结构的数据。你把所有输入按空白字符切成一个列表之后需要什么就按顺序取什么完全不用关心换行符在哪。很多竞赛选手就是这么写的代码短、速度快、可读性也好。2.3 输出格式的细节决定生死输出看似简单其实也有讲究。print() 默认会在末尾加上换行多个值用逗号分隔时自动用空格连接。比如 print(a, b) 会输出“a b\n”这在大多数情况没问题。但如果题目要求输出一个列表比如所有答案用空格分隔成一行最稳的写法是print( .join(map(str, ans)))join 的好处是不会在结尾多出一个空格也不会像循环 print(..., end ) 那样最后多一个空格。部分评测系统对行尾空格很宽容但也有系统是严格字符匹配一旦多一个空格就判 WA。为了稳妥我统一用 join。另一个常见要求是保留小数位数。Python 里用 f-string 很方便print(f{ans:.2f})不过要注意 round() 是“银行家舍入”遇到 0.5 这种边界值可能向偶数方向取整和你想的四舍五入有偏差。机试里大部分题目不会专门卡这个边界但如果你不放心可以自己加一个极小量再格式化比如 print(f{ans 1e-9:.2f})。我自己在比赛中就这么干过能避开不少奇怪的精度问题。3. 高频输入场景模板大全3.1 单行输入的四类情况单行输入是机试里最简单也最常见的形态。归纳一下无非四种单个整数、一行多个整数、一行字符串、一行混合数据。我直接把模板贴出来# 1. 单个整数 n int(input()) # 2. 一行多个整数 nums list(map(int, input().split())) # 3. 一行字符串 s input().strip() # 4. 一行混合数据比如先是整数n再是字符串s parts input().split() n int(parts[0]) s parts[1]注意第四种写法里整行先用 split() 切成字符串列表再逐个转换。千万不要写成 int(input().split())这一定会报 TypeError因为 split() 返回的是列表而不是字符串。3.2 多行输入的四种循环套路多行输入才是机试的重头戏。我总结了四种最经典的场景基本覆盖牛客和华为机试里 90% 以上的输入形态。第一种先给一个整数 T表示接下来有 T 行数据。这是最常见的“测试用例组数”模式。T int(input()) for _ in range(T): nums list(map(int, input().split())) # 处理每一组数据第二种不知道具体多少组一直读到文件结束EOF。牛客网特别喜欢这种格式经典题目比如“每行输入两个整数 a 和 b计算 a b输入包含多行”。import sys for line in sys.stdin: line line.strip() if not line: continue a, b map(int, line.split()) print(a b)这里有个小细节for line in sys.stdin 遇到文件结束会自动停止你不需要手动判断 EOF也不会抛异常。如果你非要用 input() 模拟这种效果就得写 try/except EOFError麻烦且容易出问题。第三种不确定组数但会遇到一个特殊结束符比如输入 0 0 时停止。while True: line input().strip() if line 0 0: break a, b map(int, line.split()) print(a b)这种写法要注意结束条件的字符串格式必须和输入完全一致。上面的例子假设结束符是“0 0”如果题目说“输入 0 0 时结束”那 line 去掉首尾空格后正好是 “0 0”判断没问题。第四种先给行数和列数再给一个矩阵。n, m map(int, input().split()) grid [] for _ in range(n): row list(map(int, input().split())) grid.append(row)矩阵题在华为机试里很常见尤其是 DFS、BFS 这种图的遍历题。你需要保证每一行读进来的长度和 m 一致如果不一致多半是输入里有空行混进去了提前做防御性判断会省很多调试时间。3.3 矩阵和字符串两类特殊输入矩阵输入除了上面那种标准形式偶尔会遇到没有行列数、直接给若干行的情况这时就要靠 EOF 读了import sys grid [] for line in sys.stdin: line line.strip() if not line: continue row list(map(int, line.split())) grid.append(row)字符串输入则要格外小心空格。如果题目告诉你字符串里可能包含空格比如“输入一个字符串其中可能包含空格”这时候不能 split()只能去掉末尾换行import sys for line in sys.stdin: s line.rstrip(\n) # 按行处理保留行内空格我在机试里遇到过一次很恶心的题输入是一行用引号包裹的字符串类似 hello world 这样带前后引号。这时候需要先 strip() 去掉两端空白再手动去掉首尾引号才可以拿到真实字符串。这种怪题目不多但一旦碰到平时积累的模板就能救命。3.4 万能读入解析法一梭子读完再处理如果你觉得前面的模板太多记不住我给你一个终极方案用 sys.stdin.read() 把整个输入当成字符串一次性读完然后按空白字符切分再按输入描述的顺序一个个取数。这是一种很多竞赛选手都会用的“万能读入法”。import sys def solve(): data sys.stdin.read().split() it iter(data) n int(next(it)) nums [int(next(it)) for _ in range(n)] # 继续按顺序取…… return result if __name__ __main__: print(solve())这个写法的核心思路是把输入看作一个连续的 token 流完全忽略换行和行的概念。只要你知道输入的第一个字段是 n、第二个到第 n1 个是数组元素就能通过 next() 按顺序取出来。对于输入格式复杂、行数不固定的题目这个方案往往比逐行分析简单得多。我第一次见识这个思路是在打校赛的时候隔壁老手不到一分钟就把 IO 部分写完了我当时还在一行行 if line 判断。后来我也改用这个办法刷题效率提升非常明显。当然如果你要处理的是带空格的字符串这个方案就不太灵了因为 split() 会把空格当作分隔符给切开。所以万能读入法更适合纯数字类题目字符串题还是老实按行读。4. 牛客网和华为机试的备考节奏4.1 牛客网在线编程的输入输出规律牛客网的在线编程模块把题目分成了好几个系列其中有一个“在线编程输入输出专项训练”专门练各种输入格式我强烈建议刚开始接触 ACM 模式的考生先去把这个专项刷一遍。里面的题目本身很简单很多就是 AB 的变体但输入格式花样很多正好用它们来练模板。从我的经验看牛客网常见的输入格式主要集中在三种一种是“输入多组数据每组一行”需要读到 EOF一种是“第一行为 T接下来 T 行数据”还有一种是“输入 0 0 结束”。你把这三种模板背熟牛客上的大部分题目 IO 部分基本稳了。另外牛客的判题系统对输出格式比较宽松行尾空格一般不影响但我仍然建议你严格按照题目要求输出别给自己埋雷。4.2 华为机试的ACM模式细节华为机试尤其是大家常说的 OD 机试一般考 3 道算法题时间和分制不同批次会有差异常见的大致是 100 分、200 分题目搭配总时长两小时左右各批次不完全一样。从题型风格上看字符串处理、数组/矩阵、动态规划、DFS/BFS、排序和贪心是高频方向。输入输出形式上我见过的批次大多采用 ACM 模式需要你自己处理 stdin/stdout。有些平台也会支持核心代码模式但我不建议你赌这个因为一旦遇到 ACM 模式的题你不可能现场去翻模板。考试前要做好两手准备既能写完整的读入输出也熟悉函数模式下怎么快速适配。另外华为机试有些题对输出格式有明确要求比如输出结果需保留两位小数或者多答案时按字典序输出。这类要求经常藏在“输出描述”里不仔细看就会栽跟头。我的习惯是做题前先把输入输出描述圈出来逐词读一遍再开始写代码。4.3 高效的备考顺序和刷题建议如果你时间紧我建议按下面这个节奏来第一周只练输入输出专项和简单模拟题目标是看到任何输入格式都能在 1 分钟内写出解析代码第二到第四周刷高频算法专题包括数组、哈希表、双指针、二分查找、动态规划、图遍历剩下时间做真题和模拟题而且一定要在 OJ 上提交完整程序不要只在本地 IDE 里自嗨。平时最好维护一个自己的“模板文件”把快速读入、输出格式化、常见数据结构的操作都封装好比如这样的结构import sys def solve(): data sys.stdin.read().split() # ... return ans if __name__ __main__: result solve() print(result)这样每次考试开始后先把模板敲出来再进入读题和思考阶段能省下不少时间。我实测过在机器上从头敲这段模板只需要 20 秒左右但能让你面对任何输入格式都心里有底。5. 这些输入输出的坑我替你们踩过了5.1 最常见的五类致命问题做 OJ 这些年我见过无数人包括我自己在同一批坑里反复摔跤。这里整理五类最常见的致命问题每一个都能让你的 AC 变成 WA 或者 RE。第一个是循环读入时崩溃。用 while True input() 读多行读到文件末尾没有捕获 EOFError程序直接抛异常。解决办法很简单要么用 for line in sys.stdin要么 while 循环里包 try/except EOFError。第二个是字符串带了换行符或隐藏字符。有些字符串题目要求输出原字符串但你没做 strip()输入行末尾的 \n 就被带进了结果里导致输出比对不一致。用 .strip() 或者 .rstrip(\n) 都能解决。第三个是split使用姿势不对。有人写 line.split( )但输入里可能有多个连续空格结果数组中会出现空字符串转 int 时直接报错。直接用不带参数的 split() 就可以它会连续处理多个空格和 tab。第四个是输出混入了调试信息。很多人用 print 打印中间变量提交时忘了删结果答案前后多出一堆数字或文字。在线评测系统比对的是完整输出多一个字符都会判错。第五个是输出格式不对。比如要求输出一行用空格分隔的数字你写成每次 print 一个数等于每个数之间隔了一个换行自然不正确。我把这些问题整理成速查表方便你自查现象可能原因解决方案EOFErrorwhile True input() 读到末尾改为 for line in sys.stdin输出莫名多了空行字符串带 \nstrip() 或 rstrip(\n)列表里有空字符串split( ) 遇多个空格使用无参 split()答案前后多出调试信息print 未删除提交前全文搜索 print数字之间换行而非空格循环 print 单个值用 join 拼接一行输出5.2 排查思路和自测方法遇到提交报错别慌先用本地模拟输入输出排查。最简单的方法是把样例输入保存到 input.txt然后用命令行重定向的方式运行python3 solution.py input.txt然后把输出和题目给的样例输出做对比。如果一致说明基本逻辑没问题问题可能出在隐藏数据上如果不一致你就能立刻定位到是解析错了还是算法错了。更多的隐藏数据问题可以用“小规模对拍法”来排查。写一个最简单的暴力解法再写一个针对随机小数据生成输入的脚本把两个程序的输出反复对比一旦不一致就缩小范围找 bug。这个方法虽然土但在准备机试时非常有效因为很多隐蔽错误只有在特定数据下才会暴露。5.3 我的一点独家心得除了上面的常规坑还有几个小技巧是很多人不注意的。第一转 int 之前先判断字符串是不是数字可以用 str.isdigit()不过负数带负号时这个方法不好使最好直接用 int() 并配合 try/except。第二如果输出结果需要对浮点数取整尽量不要依赖 round()用 format 或 f-string 更可控。第三读入很大时可以给 sys.stdin 设置缓冲区但默认缓冲已经够用不需要额外折腾。我还会在本地模板里提前写好一个 DEBUG 开关import sys DEBUG False def solve(): if DEBUG: sys.stdin open(input.txt, r, encodingutf-8) data sys.stdin.read().split() # ... if __name__ __main__: print(solve())平时把 DEBUG 改成 True直接从 input.txt 读数据提交时改成 False就恢复正常标准输入。这样既不会忘删文件读取代码又能快速切换对我来说是效率最高的方式。6. 本地调试与提速实操6.1 用文件重定向模拟在线评测在正式机试前我强烈建议你养成“本地文件模拟评测”的习惯。具体做法是先创建一个 input.txt把题目给的样例输入原样放进去然后在命令行下运行python3 solution.py input.txt这样你的程序就会从 input.txt 读取输入而不是等待键盘输入。这一步看起来简单却能提前发现很多致命问题比如读入死循环、换行符处理错误、输出格式不对等。我在准备华为机试的那段时间几乎每道题都会先用这种方式自测一遍发现问题的效率比单纯盯着 IDE 高很多。如果你用的 IDE 不便于命令行操作还可以在代码里把 sys.stdin 替换成文件对象。记住无论是重定向还是文件替换提交到 OJ 前都一定要改回来否则程序会找不到文件而报错。6.2 把解析和算法逻辑分开写很多新手喜欢把读入、解析、计算、输出全揉在一起结果代码一长就乱出 bug 后找不到问题。我推荐一个更清晰的代码组织方式把“读入数据”和“算法逻辑”拆成两个部分中间通过参数传递。比如import sys def solve(nums): # 只关注算法逻辑nums 是已经解析好的数据 return sum(nums) def main(): data sys.stdin.read().split() nums list(map(int, data[1:])) # 假设第一个数是数组长度 print(solve(nums)) if __name__ __main__: main()这样写的好处有三个第一算法函数可以独立测试直接调用 solve([1, 2, 3]) 就能看结果第二以后遇到同类输入格式直接复制 main 函数里的解析代码第三如果题目改成核心代码模式你只需要把 solve 函数剥离出去适配非常快。6.3 读取性能实测对比最后说说性能。很多人以为 input() 和 sys.stdin 只在“很极端”的数据下才有区别其实这距离机试并不远。我做过一个小实验生成 100 万行数字每一行一个数分别用 input() 循环读和 sys.stdin.read() 全量读前者耗时差不多是后者的 3 倍以上。在内存不紧张的情况下sys.stdin.read() 永远是更快的那一个。不过也要提醒sys.stdin.read() 会把全部输入读进内存如果输入有几百 MB机试一般不会这么大才需要担心内存问题。正常机试数据规模下这个方案完全没问题。为了保险我习惯在读入后立刻把大字符串转成列表让解析尽早完成避免后续逻辑里反复做字符串操作。想亲手测性能的话可以用 time 模块简单计时import sys import time start time.time() data sys.stdin.read().split() print(fread cost: {time.time() - start:.3f}s)建议你在本地跑一次这个对比亲身体会一下差距之后写代码就会下意识选择更高效的读入方式。最后说点个人感受。我最早在牛客网刷题的时候也是一顿操作猛如虎提交一看各种 0 分后来才发现全是输入输出的问题反而算法思路本身没有错。那之后我花了整整两天时间把常见输入格式的模板全部背下来又用本地文件和重定向的方法反复自测后面再参加华为机试就再没在 IO 上栽过跟头。输入输出这东西说难也不难但它就像游泳前的热身不做好下水就容易抽筋。花点时间把模板和习惯练好你在考场上就能把全部脑力留给真正的算法题这笔时间花得非常值。
返回列表