
PTA 的 Python 题库第二章是很多人刷题路上第一次“被教育做人”的地方。题目看着都不难无非是打印点东西、算个数、循环求和可真正提交的时候非零返回、格式错误、答案错误却轮着来一晚上卡在同一道题上也不奇怪。这篇文章把常见的 2.1 到 2.14 这十四道题放到一起讲重点不放在“逐行贴答案”而是把题里反复出现的几类考法拆开输出格式为什么这么严格、input 读进来的到底是个什么东西、浮点数格式化怎么才不会翻车、循环求和的边界在哪、列表和图形输出怎么一次写对。适合刚学完 Python 基础语法、准备把这一章刷满分的初学者也适合期末复习时快速过一遍考点。如果你已经刷完但不知道自己错在哪可以直接跳到后面第 6 节那里整理的报错排查表基本都是平时最常见的坑。1. 整体设计这 14 题到底在考什么1.1 知识点分布规律如果只看单题你会觉得题目之间跳跃很大一会儿输出图形一会儿算温度一会儿又让你求个级数和。但站在整章视角第二章其实是在搭建 Python 程序的基本骨架核心就是三件事输入怎么读、计算怎么做、结果怎么输出。为了让你能对号入座我按主流 PTA Python 题库的常见编排方式给 2.1~2.14 做了一张知识点分布表题号区间核心语法典型失分点2.1-2.3print 输出、字符串拼接、简单算术多打或少打空格、中文引号、忘记换行2.4-2.6input、类型转换、浮点数格式化忘记 int()/float()、小数位精度不对2.7-2.9分支结构、range 循环、累加器边界多算一个、elif 顺序写反2.10-2.11循环与字符串、简单数列初始化位置不对、死循环2.12-2.14字符串逆序、列表统计、图形输出空输入没保护、行尾多一个空格这套题目的编排逻辑是典型的高校入门教材思路每道新题只在上一个知识点上加一点东西。第 2.1 题如果只是“背下样例输出”混过去到 2.2、2.3 题就会开始难受因为后面的题目不再允许你抄答案而是让你根据输入动态组织输出。我在带人刷这章时经常说一句话第二章刷的好坏不在于你记住了多少代码而在于你有没有把“读数据、算结果、按格式输出”这个流程变成肌肉记忆。后面第三章的函数、第四章的列表操作全都建立在第二章这套动作之上。1.2 刷题顺序建议很多同学喜欢从头到尾一道一道做卡住了就死磕。我的建议是把 14 道题分成四个小阶段每个阶段解决一个明确问题热身阶段2.1~2.3先把 print 的各种用法摸熟不追求速度追求“每次运行结果都和心里想的一样”。计算阶段2.4~2.6集中练输入拆分和格式化输出这一阶段最容易养成分步调试的习惯。分支循环阶段2.7~2.11这几道题值得写三遍。第一遍完全不看资料第二遍对着报错改第三遍尝试把代码压缩到 10 行以内。综合阶段2.12~2.14把前面所有知识点串起来重点练“先构思再写码”。每个阶段结束时不要急着往下刷先在本地把本阶段所有题的样例重新跑一遍确认输出和题目样例完全一致。这个小习惯能帮你把知识漏洞控制在最小范围而不是等到第三章才一起暴露。2. 输出与格式化专题2.1、2.2、2.12 这类题坑全在看不见的字符里2.1 print 的三兄弟逗号、sep 和 end输出类题目看起来最简单实际上失分率不低。原因很简单PTA 的判题不是人眼对照而是把你程序的输出和期望输出做逐字节比较。你以为“差不多就行”机器可完全不讲情面行尾多一个空格、少一个换行都会被标记为格式错误。print 函数有三个容易被忽略的参数。第一个是 sep控制多个输出项之间的分隔符默认是一个空格第二个是 end控制打印结束后的字符默认是换行第三个是文件对象平时不常用但要知道它存在。看下面这段代码print(Hello, World) print(Hello, World, sep-) print(Line1, end ) print(Line2)第一行输出Hello World中间是空格第二行输出Hello-World分隔符变成了短横线第三、四行因为把 end 改成了空格所以Line1和Line2会出现在同一行。很多人在 2.1、2.2 这类图形输出题里写了一大堆 print结果行首或行尾多出空格就是这个原因。输出固定图形时最干净的写法不是一个个字符去 print而是用字符串乘法。比如要输出一个由 5 个星号组成的行print(* * 5)这段代码直接用字符串乘以整数生成 5 个星号正好符合题目要求。如果题目要求“行尾不能有多余空格”那你更不能用print(* * 5)这种写法因为行尾那个空格会直接导致格式错误。2.2 格式化字符串三选一计算类题目里输出往往要求保留若干位小数。Python 里常用的格式化方式有三种百分号格式化、format 方法和 f-string。下面这段代码三种方式写出来的结果完全一样name Python score 87.5 print(%s: %.1f分 % (name, score)) print({}: {:.1f}分.format(name, score)) print(f{name}: {score:.1f}分)我的建议是优先用 f-string。它在 Python 3.6 以后都支持写法最直白可读性也最好。但有个地方要特别注意{score:.1f}里的冒号后面跟的是格式说明符.1f表示保留一位小数。如果你写成了{score:1f}那结果会完全不一样甚至报错。很多刚接触的同学会误以为处理小数显示用round()就行这其实是个大坑。round()做的是四舍六入五成双的银行家舍入而且它不负责补位。比如round(3.14159, 2)得到 3.14但如果答案是 3.10round(3.1, 2)只会输出 3.1不会自动补成 3.10。PTA 判断的是输出字符串3.1 和 3.10 是两种完全不同的结果所以显示层面的事一律交给格式化字符串处理不要交给 round。2.3 字符串逆序切片一行解决2.12 附近经常出现一类题给你一个字符串要求逆序输出。最简单的写法是切片s input().strip() print(s[::-1])s[::-1]的意思是从头到尾取值但步长为 -1也就是倒着取。这里有个隐藏考点input() 读进来的字符串末尾可能带着换行符但 input 本身会去掉末尾换行所以不用太担心。不过如果题目要求按字符逐行输出写法就变成for ch in reversed(s): print(ch)使用reversed(s)而不是s[::-1]的原因是前者生成迭代器内存占用小后者会生成一个新字符串。对第二章的题目来说两者差别可以忽略但养成用迭代器的习惯对后面处理大文本有好处。如果你被“逆序”这个需求绕晕可以拿生活里的场景来类比切片就是一条列表[::-1]相当于让你从队尾往队头走每一步往前挪一个位置最后把沿途看到的人按顺序报出来。这个思路理解了后面二维列表的翻转操作也一通百通。3. 输入处理与基础运算2.3~2.6先弄清楚回车到底变成了什么3.1 一行输入两个数为什么总有人卡在拆分上PTA 的题目描述里经常出现这样一句话“输入在一行中给出两个正整数 A 和 B”。很多新手第一次看到这种描述会以为“一行”就是写两个 input()。实际不是。程序运行时input()每次只读取一行如果一行里有多个由空格分隔的数据你得自己把它们拆开。a, b map(int, input().split()) print(a b)这段代码是这类题的万能开头。它做了三件事input()读取整行字符串.split()按空白字符把字符串切成若干段map(int, ...)把每一段转成整数最后用两个变量分别接收转换结果。很多人会在这里踩一个隐蔽的坑为了“按空格切分”写成input().split( )。这个写法在数据之间只有一个空格时没问题可一旦两个数之间不小心出现了两个连续空格split( )就会切出一个空字符串int()直接报错。而默认的.split()会把连续空白符当成一个分隔符处理所以永远优先用默认写法。另外当你拿到的不是一个定长的输入行而是若干个由空格分隔的数时用map接收会更好nums list(map(int, input().split()))这样得到的就是一个整数列表后面再统计和、找最大最小都很方便。第 2.13 这类列表题基本都会用到这行代码。3.2 温度转换和圆面积里的浮点格式化第二章至少有三分之一的计算题本质都是在考察“输入两个数或一个数套公式按格式输出”。以常见的温度转换题为例已知华氏温度 F求摄氏温度 C公式是C (F - 32) * 5 / 9题目可能要求保留两位小数。f float(input()) c (f - 32) * 5 / 9 print(f{c:.2f})这里有两个容易丢分的点。第一输入的温度可能是小数如果你用了int()小数部分会被直接截掉后面算出来的结果自然不对第二输出时如果题目写的是“结果为 37.78”那你必须原样输出 37.78不能输出 37.8更不能输出 37.779999999999994。浮点数在计算机里本身是不精确的所以“保留两位小数”这种显示需求必须放到最后用 f-string 或者 format 去处理。不要在计算中途就对中间变量做格式化那样会导致后续计算基于一个不精确的字符串反而容易出错。圆面积、球体积这类题也一样先说清楚“输入半径”然后用float(input())接收再通过math.pi或直接3.1415926计算最后统一格式化输出。具体用哪个圆周率精度以题目描述为准如果题目没有明确建议用math.pi。3.3 数字各位求和两种思路聊边界有一类题让你“读入一个整数输出各位数字之和”。比如输入 123输出 6。这道题有两种经典解法。第一种是完整走字符串路线s input().strip() total sum(int(ch) for ch in s) print(total)把读进来的内容当成一串字符逐个字符转成整数再求和。优点是完全不管数字长度哪怕输入 100 位也能算。第二种是纯数学路线num int(input()) total 0 while num 0: total num % 10 num // 10 print(total)每次用num % 10取出个位数累加后再用num // 10把数位右移一位。这里有个很重要的边界如果输入是 0第二种方法的 while 循环一次都不进total 保持 0输出结果碰巧是对的如果输入是负数比如 -123while 循环也一次不进total 还是 0那答案就错了。所以用取模法时最好先取绝对值num abs(int(input()))字符串路线则没有这个烦恼它逐字符处理负号时int(-)会报错所以也要先判断一下。但大多数题目的输入都明确是正整数这个问题只在极少数情况里出现。能在做题时主动想到这种边界条件本身就是一种能力。4. 分支与循环2.7~2.11最容易“浪费时间”的五道题4.1 分支题的核心是先画区间再写代码从 2.7 开始题目开始要求“根据条件输出不同结果”比如成绩分档、奇偶判断、区间判断。这类题看着是语法问题其实是逻辑问题。一个常见的翻车点是多个 if 并列使用导致一个输入同时满足多个条件输出多行。score int(input()) if score 90: print(A) elif score 80: print(B) elif score 70: print(C) else: print(D)这段代码的关键在elif。只要第一个条件成立后面的分支就不会再判断这保证了每个输入只有一个输出。写分支题的正确顺序是先把题目里的条件画成一条数轴标出各个区间再决定从哪头开始写。如果你从中间开始写很容易出现区间覆盖不全或重复覆盖的情况。比如成绩题应该先判断最高区间然后依次往下这样边界值 90 和 80 的归属一目了然。还有一类题会要求你判断输入是否合法。比如“成绩在 0 到 100 之间才有效否则输出 Invalid”。很多人会把合法判断写在最后结果 101 分被当成 A。正确的写法是把非法情况放在最前面score int(input()) if score 0 or score 100: print(Invalid) elif score 90: print(A) else: print(B)这种“先排除异常再处理正常”的顺序可以减少很多逻辑漏洞。4.2 range 的边界少 1 是常态多 1 是病循环题从 2.8 开始密集出现最常见的是“求 1 到 N 的和”“求 1 到 N 内所有偶数的和”。它们的核心都是 range 函数。n int(input()) total 0 for i in range(1, n 1): total i print(total)range 的半开区间特性是新手最容易踩的坑range(1, n)只会取 1 到 n-1根本不会取到 n。想包含 n就必须写成range(1, n 1)。计算这类题时我有个自己一直在用的笨办法先把边界值代进去试算。比如输入 5心算一下 1234515然后跑代码看输出是不是 15。如果是 10说明漏掉了最后一个数如果是 20说明多算了一个数。这种“代入小样例”的做法能帮你在几秒钟内抓住 range 边界错误比反复读代码快得多。如果题目用的是 while 循环那就更要注意累加变量的初始化和递增是否在循环体内n int(input()) i 1 total 0 while i n: total i i 1 print(total)很多人把i 1写在循环外面或者干脆忘写结果程序无限循环在 PTA 上直接超时。这种问题在本地调试时看不出来因为终端里程序卡住了你会以为是自己在等输入实际上是死循环。4.3 浮点累加要输出才格式化不要中途比较2.9、2.11 这类题经常出现分数或小数求和比如求1 1/2 1/3 ... 1/N的前 N 项和。写起来不难但存在一个隐藏的浮点误差问题。n int(input()) s 0.0 for i in range(1, n 1): s 1 / i print(f{s:.6f})这里的s在循环里不断累加每次累加都有微小的舍入误差。如果我在循环中间写一个if s 1.5之类的判断很可能会出现永远判断不上的情况。原因就是1/2 1/3的结果并不是干净的 0.833333而是 0.8333333333333334。处理办法很简单不要在循环中间比较浮点数的值只在最后输出前做一次格式化。格式化会按照你指定的精度把数值变成字符串这一过程会自动处理舍入。如果题目要求你“精确到小数点后两位”那你完全不用担心中间误差因为最后格式化已经帮你把可见误差抹掉了。但如果你天真地写s round(s, 2)然后继续累加反而会让后续计算偏离真实值更远。记住一个原则round 管最终显示不管中间过程。5. 列表选最大最小和图形打印2.13、2.14 的通用套路5.1 一读进来就能用 max 和 min何必自己排序2.13 这类题常常给你一串数字让你挑出最大值和最小值。很多同学第一反应是“先排序再取头尾”这个思路没错但绕远了。Python 内置的max()和min()可以直接搞定。nums list(map(int, input().split())) print(max(nums)) print(min(nums))如果题目要求的是“先读入一个 N再读入 N 个数”那读法略有不同n int(input()) nums list(map(int, input().split()))你可能会担心输入的 N 和实际数字数量对不上PTA 的数据通常能保证一致所以不用自己做二次校验。还有一种常见变形是“按输入顺序去重并输出”。这类题可以用列表操作模拟一个“已见过”的集合nums list(map(int, input().split())) seen [] for x in nums: if x not in seen: seen.append(x) print( .join(map(str, seen)))5.2 金字塔、等腰三角、菱形的统一公式图形输出题在第二章里非常经典而且变化多端直角三角形、等腰三角形、菱形、空心图形。很多人看到这类题就慌因为每个图形的空格和星号数量都不一样。但实际上所有这类题都可以用一个模板解出来。以等腰三角形为例输入行数 n输出一个由星号组成的等腰三角形n int(input()) for i in range(1, n 1): print( * (n - i) * * (2 * i - 1))关键在于推导每一行的空格数和星号数之间的关系。第 i 行空格数是 n-i星号数是 2i-1。这个公式从第一行开始代入就能验证第一行是 21-11 个星号第二行是 3 个星号以此类推。我总结过一套从零开始推公式的方法先在纸上画出一个 3 行或 4 行的三角形把每行的空格数和星号数列成一个表再找规律。这个方法虽然原始但能帮你把“凭感觉写”变成“按公式写”准确率立刻提升。5.3 行末尾空格怎么处理图形题里有个特别容易忽略的细节题目如果要求行内用空格分隔但行尾不能有多余空格那你需要额外处理。比如输出一行由数字组成的列表你可能会写for i in range(1, n 1): print(i, end )这会在行尾额外输出一个空格某些严格的判题系统会直接判格式错误。更稳的写法是先把整行内容拼成字符串再一次性打印出来row .join(str(i) for i in range(1, n 1)) print(row)用join的方法行与行之间的空格全部由字符串连接生成天然不会有多余尾空格。图形题里的星号之间需要用空格分隔时也推荐先把一行的各部分放进一个列表然后 join。这个方法虽然比逐个 print 多一步但换来的稳定性非常值得。尤其是碰到那种“全对但显示格式错误”的白色小叉十有八九就是行尾空格问题。6. 常见问题与排查技巧实录6.1 提交前两分钟自查清单我在刷完第二章之后给自己总结了一份提交前自查清单后来分享给身边的同学他们说还挺管用。每次提交前按下面几步快速过一遍确认题目要求输入的数据类型是 int 还是 float别呆板地全用 int。确认 input() 的调用次数和题目输入描述一致不要多读一行。确认输出是否有固定提示文字如果有连标点符号都要一致。确认是保留两位小数还是六位统一用格式化字符串控制。确认循环里不会出现死循环检查累加变量是否更新。确认行尾没有多余空格行间没有多余空行。这六条只要全部过关大部分提交失败都是逻辑问题而不是低级格式问题。6.2 高频报错速查表第二章的报错其实非常固定下面这张表是我在实际刷题过程中整理出来的遇到报错可以按表排查报错现象实际原因解决方式NameError: name ... is not defined变量名拼写不一致或忘记定义优先检查代码中的拼写注意大小写ValueError: invalid literal for int() with base 10试图把非数字字符串转成整数先用 print 查看 input() 实际返回内容TypeError: can only concatenate str (not int) to str字符串和数字直接用加号拼接用 str() 转换或格式化字符串EOFError: EOF when reading a line程序在输入结束之后还调用 input()检查循环次数是否多一次显示格式错误输出和期望输出有空格、换行差异用 repr 查看输出重点看尾部非零返回Non-zero exit code程序中途崩溃通常是某类输入没考虑到复现题目边界数据查看崩溃堆栈ValueError 是第二章里最常见的报错之一。读者第一次看到invalid literal for int() with base 10时往往会懵其实翻译一下就懂了int()在把某个字符串转整数时失败了因为这个字符串不是整数。绝大多数情况是 input 里混入了空格、空字符串或者字母。你可以先写一句打印把输入内容看个究竟再决定怎么处理。6.3 格式错误到底错在哪用三招定位格式错误是第二章最磨人的坑。它的特点是你辛苦写的代码逻辑完全正确但就是显示红色叉。定位这种问题我一般用三招。第一招用repr()查看字符串的真实样子。repr()会把字符串里的换行符和空格都显示出来比如37.78\n。这样你就能看出输出末尾是不是多了一个空格或者换了两行。print(repr(f{c:.2f}))第二招把题目样例的输出复制到一个文本文件里再把你本机的输出复制到另一个文件用 diff 命令或编辑器自带的对比功能比较。diff 会精确标出第一个不同字符的位置效率极高。第三招检查自己是不是用中文标点替换了英文标点。这个坑在图形输出题里特别常见你明明写了空格屏幕上看起来也对但输出里的“空格”可能是个全角空格机器一看就不是同一个字符。6.4 刷第二章的节奏建议最后聊点刷题节奏的事。第二章一共 14 道题如果你是完全的新手我建议每天 3 到 4 道不要贪多。每道题提交前先在本地用题目给的样例测试一遍如果样例能过再想一想“如果输入是边界值会不会炸”。比如输入 0、输入 1、输入一个很大的数这些情况 PTA 很可能都会测到。卡题超过 40 分钟就果断跳过先做后面的。这不是逃避而是让大脑换个问题反而容易在回头时找到思路。至于看题解我的建议是先自己写出一个能运行的版本再去看题解看的时候重点对比“我哪里绕了远路”而不是把代码抄下来提交。这一章的所有题目本质上都只是后面章节的地基地基打得稳第三章和第四章才不会越刷越痛苦。我自己第一次刷这章时也犯过把range(1, n)当成包含 n 的低级错误也被“行尾空格”折磨到怀疑人生。后来养成了两个习惯所有输出统一用 f-string 格式化所有带循环的题先代一个极小样例心算。这两个习惯让我之后刷列表、字典和函数时省下了大把排查时间。如果你现在正被第二章卡住别急着怀疑自己先按上面的排查清单走一遍多半就是某个看不见的字符或边界条件在捣乱。把这一关耐心过完后面会顺很多。