☰
Python OJ刷题入门:边界条件与输出格式避坑指南
2026/10/16 3:44:48 网站建设 项目流程

刷某高校OJ平台Python题库的时候,我最深的感受不是“题目难”,而是“题目看着不难,提交上去就是过不了”。第二章2.1~2.14属于入门阶段的分水岭——语法你都认识,if、for、while、列表、字符串好像都会,但真正按平台的要求把题做对,靠的却是另一套东西:边界条件、输出格式、浮点精度、读题习惯。这篇题解,就是把我当时在这14道题上踩过的坑、琢磨出来的套路,以及每道题背后的判题逻辑,完整拆开来讲。如果你正在刷这个题库,或者刚开始接触OJ判题系统,这篇文章应该能帮你少走不少弯路。

1. 第二章2.1~2.14到底在考什么:一个从入门到拐弯的知识梯度

1.1 从题目分布看Python基础语法的考点

先说清楚这一章的整体构造。2.1到2.14一共14道题,表面上是在考“Python基础语法”,但细看就会发现难度并不是均匀上升的,中间明显有三段节奏:

题号范围主要考点给我的感觉
2.1~2.3分支结构、数学计算、格式化输出送分题,但藏着边界条件的坑
2.4~2.5循环结构、迭代逼近、数列求和开始需要“想清楚再写”
2.6~2.9字符串、列表、温度/时间/四则运算考的是读题和对内置函数的使用
2.10~2.14字符统计、去重、面积计算、斐波那契等开始出现复合逻辑,需要组合多种知识点

我刷题的时候习惯先把整章题目扫一遍,标出“一眼会做的”和“得想一想的”。2.1、2.2、2.3属于前者,2.4、2.5、2.11、2.14属于后者。但让我意外的是,真正卡住我的不是那些“得想一想”的题,反而是2.1这种“一眼会做”的分段函数——原因后面细说。

1.2 一个被很多人忽略的事实:第二章的重点不是语法而是“约束”

新手刷这类OJ题库,最容易陷入的误区是:把题目当练习册做,写完本地跑一遍,输出对了就觉得自己会了,结果一提交,被判Wrong Answer或者格式错误,整个人懵掉。

这里要先给刚接触OJ平台的朋友补一个背景。这类在线判题系统的工作方式,简单来说就是:你的程序读入一组或多组输入数据,程序输出结果,平台拿你的输出和标准答案做逐字符比对。注意是逐字符比对,多一个空格、少一个换行,都算错。

所以第二章真正训练你的,并不是“Python语法”,而是三件事:

  1. 精确读题:题目说“在一行中输出”,你就不能输出两行;题目说“保留两位小数”,你就不能输出三位。
  2. 处理边界:分段函数的临界值究竟归入哪一段,循环的起始值该从0开始还是从1开始,这类问题才是隐藏的扣分点。
  3. 控制输出:能不用print默认的多个参数分隔就不乱用,该用格式化字符串就规规矩矩地用。

这篇文章的题解顺序,我就按照题目难度和类型重新组织,不按题号硬排。4道题一组,把它们的共同套路和各自的陷阱放在一起讲,这样比一道一道孤立地看更有效果。

2. 分段函数、阶梯电价与成绩转换:分支题的三个经典陷阱

2.1 分段函数:else分支与浮点陷阱

先讲2.1。这类分段函数题目,一般长这样:给你一个自变量x,根据x的取值范围套用不同公式计算y值,然后输出。

我一开始写的版本是这样的:

x = float(input()) if x < 0: y = x ** 2 + 1 elif x > 0: y = x ** 3 + 2 else: y = x + 1 print(y)

本地测试,输入-1、0、1,输出都像是“正常的”。但提交之后发现,题目要求的是“如果 x 小于 0 用公式A,如果 x 大于等于 0 且小于 10 用公式B,否则用公式C”,我写的却是“x < 0”和“x > 0”,把x=0这个点漏掉了。更隐蔽的是,如果题目还要求“x等于某个临界值”时走另一个分支,很多新手直接用elif x >= 0去接,看起来没错,但如果前面已经有if x < 0的分支,那么elif x >= 0其实等价于elif x > 0,等于边界值还是被漏掉。

这里要记住一个原则:分段函数题,边界条件必须显式写出。我的习惯是先画出数轴,把临界点标出来,再确定每个区间是开区间还是闭区间。判断条件写成if x < 0:、elif x == 0:、elif x > 0:这种完整覆盖的形式,而不是靠“非A即B”的隐含逻辑。

另一个常见问题是浮点精度。如果题目要求保留某位小数,直接print原始浮点数可能因为Python的浮点表示带来尾数误差。比如0.1 + 0.2在Python里是0.30000000000000004。所以无论用什么公式计算,输出前一定要格式化:

print(f"{y:.2f}")

提示:如果题目没有明确说明保留小数,那就按原样输出;一旦看到“保留N位小数”这几个字,第一时间想到格式化字符串,不要偷懒。

2.2 阶梯电价:区间重叠最容易写错的if-elif链

2.2这道题是典型的阶梯计价:用电量在某个范围内按第一档价格算,超过部分按第二档价格算,以此类推。这类题目的难点不是公式,而是“区间边界”和“重复计费”两个问题。

很多新手会写成:

electricity = float(input()) if electricity <= 50: cost = electricity * 0.538 elif electricity > 50: cost = electricity * 0.538 + (electricity - 50) * 0.05

这个写法在第一档边界没问题,但第二档的算法其实是错的。正确的阶梯计费逻辑是:50度以内的部分按第一档算,超过50度的部分才按更高的一档算。上面的写法把整段用电量都按第一档算了一遍,再把超出部分补差价,这确实是“先全部算基础价+补差价”的等价做法,而且比直接分段计算更不容易出错。但问题出在第三档——如果题目有第三档,elif electricity > 50这个条件会拦截掉所有大于50的情况,第三档永远走不进去。

正确的if-elif链,边界应该从小到大逐个闭合:

electricity = float(input()) if electricity <= 50: cost = electricity * 0.538 elif electricity <= 100: cost = 50 * 0.538 + (electricity - 50) * 0.588 else: cost = 50 * 0.538 + 50 * 0.588 + (electricity - 100) * 0.838

注意这里的写法:第一档完整覆盖<=50,第二档用elif electricity <= 100接住50到100之间的部分,第三档else接住大于100的部分。因为elif天然顺序取第一个满足条件的分支,所以不需要再写electricity > 50 and electricity <= 100这种冗余条件。

我在刷这题时还遇到一个输出格式问题:电费可能是整数,比如用电量正好是50,输出0.538乘以50得到26.9,题目可能要求保留两位小数,也可能不要求。一旦要求保留小数,题目输出样例里看到的26.90和26.9在判题系统里是两个完全不同的字符串,所以必须严格按题目要求的格式来,多一位零都不行。

2.3 百分制成绩转换五级制:从多个分支到算法思维

2.3这道题一般是输入一个百分制成绩,输出对应的等级。最朴素的写法不用动脑子:

score = int(input()) if score >= 90: print("A") elif score >= 80: print("B") elif score >= 70: print("C") elif score >= 60: print("D") else: print("E")

这段代码没有任何问题,也很好理解。但我更推荐在这类简单题里多想一步:能不能用数学方式直接映射?

比如等级边界是90、80、70、60,那可以这样写:

score = int(input()) level = "EEEEEEDCBAA" if score < 0 or score > 100: pass else: index = score // 10 print(level[index])

当然这个写法有点投机取巧,字符串索引得看准。实际上我更喜欢用字典和区间判定结合起来,但在这类入门题里,我的建议很简单:先用最直白的写法把题做对,然后再思考有没有更漂亮的写法。因为判题系统只认输出,不认代码风格。先把对的方法跑通,再去优化,这才是刷题的正确节奏。

不过有一个坑必须提:输入成绩不一定在0到100之间,可能超出范围,也可能带小数。有些题目会明确说“假设输入在有效范围内”,那就不用管;如果没说,最好加一个边界判断。做这类OJ题,永远以题目描述为准,不要自己脑补条件,这是新手最容易犯的错。

3. 数列求和、平方根与斐波那契:循环题的三个经典套路

3.1 特殊a串数列求和:字符串与整数的互换

2.4这类题目很经典:输入a和n,求a + aa + aaa + ... + a...a(n个a)的和。比如a=2, n=3,就是2 + 22 + 222 = 246。

第一反应是每项生成一个整数,累加:

a, n = map(int, input().split()) total = 0 term = 0 for i in range(n): term = term * 10 + a total += term print(total)

这其实是最优解。term = term * 10 + a这个递推公式,每次把原来那项往左移一位,再加上新的a,就能依次生成a、aa、aaa。这里的关键是理解“字符串拼接”和“整数计算”之间的关系。

但也有些新手会写成:

a, n = map(int, input().split()) total = 0 for i in range(1, n + 1): total += int(str(a) * i) print(total)

这个写法用str(a) * i把数字转成字符串再拼接,然后转回整数。从结果上讲完全正确,而且读起来更直观。问题是当n比较大的时候,字符串拼接再转换的效率虽然也不差,但显然没有整数递推那么“正宗”。更重要的是,下一道类似的题如果变成“字符串拼接后再做某种操作”,这个套路就非常有用。所以两种都该会写。

这道题我实际测试的时候,最容易错的反而是输入格式。题目可能要求在一行中输入两个正整数a、n,也可能分两行输入。我一开始没仔细读题,按两行输入写了,结果本地测试时手动输入没问题,但提交后平台给的测试样例是多组测试,程序读第一行就中断了。所以遇到input()读取,一定要先数清楚题目里有几个输入,要不要用split()一次性读干净。

3.2 平方根:迭代收敛的判停条件

2.5这道题一般用迭代法求平方根。比如用牛顿迭代法,或二分法。很多初学者第一次接触“迭代逼近”这个概念,会觉得有点绕:为什么要这么麻烦?直接用math.sqrt()不好吗?

OJ题库里出这种题,目的是让你理解“数值计算中的逼近思想”,而不是真的让你实现一个比标准库更快的开方函数。但既然题目这样出了,我们就老老实实掌握其中的逻辑。

牛顿迭代法的核心公式是:

x_{n+1} = (x_n + a / x_n) / 2

我一直觉得这个公式可以用“平均逼近”来理解:如果你猜了一个近似值x,那么a除以x得到另一个近似值,这两个值一个偏大一个偏小,取它们的平均值,就会比原来的猜测更接近真实的平方根。不断重复这个过程,x就会收敛到√a。

写代码时,判停条件可以有两个选择:

  1. 固定迭代次数:比如迭代100次,够精确但不够优雅。
  2. 相邻两次结果之差小于阈值:这是更标准的做法。
a = float(input()) x = a / 2 while True: next_x = (x + a / x) / 2 if abs(next_x - x) < 1e-6: break x = next_x print(f"{next_x:.6f}")

这里我一开始栽过的跟头是:判停条件写成了while abs(next_x - x) > 1e-6,然后在循环末尾更新x,这没问题。但如果循环体里先更新x再计算next_x,就可能导致错误。还有就是初始值的选取。x = a / 2在a比较小的时候其实是不错的起点,但如果a是0或者负数,牛顿迭代法会出问题。题目一般会声明“输入为正数”,但如果没有声明,最好先处理a等于0的情况,否则会出现除零错误。

另外,输出精度非常关键。如果题目要求保留6位小数,print(f"{next_x:.6f}")就够用。但如果你通过round(next_x, 6)来四舍五入,会有一个隐藏问题:round返回的是浮点数,Python的浮点显示机制可能会把它显示成1.414214,也可能显示成1.4142140000001。所以最稳妥的方式永远是格式化字符串,而不是round。

3.3 斐波那契:循环里的前项与后项滚动

这一章后段大概率会有一道斐波那契数列题,输入n,输出前n项。最基础的两个写法:列表法和滚动变量法。

列表法适合n不大,且后续可能需要索引访问的场景:

n = int(input()) fib = [0, 1] for i in range(2, n): fib.append(fib[i - 1] + fib[i - 2]) print(fib[:n])

这个写法很直观,但要注意:如果题目要求输出前n项,而第一项是1而非0,你的初始列表和循环范围都要跟着调整。这就是“题目微小的表述差异”可能导致完全不同输出的典型案例。

滚动变量法更省内存,也更考验逻辑:

n = int(input()) a, b = 0, 1 result = [] for i in range(n): result.append(b) a, b = b, a + b print(" ".join(map(str, result)))

这里a, b = b, a + b的优雅之处在于,它同时完成“把上一项往后推”和“计算新一项”两个动作。Python里的多重赋值是同时发生的,右边先算,再分别赋值,所以不会出现需要用临时变量的情况。我第一次写的时候用的是普通写法:

a = b b = a + b

结果发现b变成了2b而不是前两项之和,因为第一行把a改掉之后,第二行的a已经不是原来的a了。这个错误很多人会犯,恰好说明Python多重赋值的语义值得铭记。

4. 列表、字符串与输出格式:样例全过依然0分的隐藏原因

4.1 列表去重:用字典记录出现次数保持顺序

这一章后面的题目里,列表去重几乎是必考。题目一般要求:对给定的列表去重,同时保持元素出现的原始顺序。

很多新手第一反应是:

data = list(map(int, input().split())) result = list(set(data)) print(result)

如果你真这么提交,大概率会错。因为set是一个无序集合,去重后的顺序是乱的,完全不满足“保持原始顺序”的要求。题目样例如果刚好是[5, 2, 3, 5, 4],用set去重可能得到[2, 3, 4, 5],跟期望的[5, 2, 3, 4]完全不同。

保持顺序的正确做法是用一个列表和一个集合/字典配合:

data = list(map(int, input().split())) seen = set() result = [] for num in data: if num not in seen: seen.add(num) result.append(num) print(" ".join(map(str, result)))

这里seen负责查重(O(1)复杂度),result负责维护顺序。也可以用字典dict.fromkeys(data)来一步到位,它既保留顺序又自动去重,而且代码更简洁:

data = list(map(int, input().split())) result = list(dict.fromkeys(data))

dict.fromkeys的这个特性在很多场景下都很实用,但前提是你理解字典的有序性——在Python 3.7以后,字典默认保持插入顺序。如果你用的环境是Python 3.6以下,这个方法可能不保证顺序,所以最好还是手写循环。

4.2 字符统计:题目只可能要求ASCII字符

另一类经典题是输入一行字符串,统计其中字母、数字、空格或其他字符的个数。新手可能会纠结:“要不要考虑中文字符?要不要考虑标点符号?”

我的经验是:不要自己加戏。OJ平台这类题目的输入几乎都是ASCII字符,你要做的只是按题目要求逐类统计。最坑的往往是input()和sys.stdin.read()之间的区别。

如果题目说“输入一行字符串”,那input()就够了。但如果题目说“输入一段文本”,里面可能包含多行,那么input()只会读第一行,后面的内容全丢了。这种情况需要用到:

import sys text = sys.stdin.read()

统计时要注意换行符的处理。sys.stdin.read()会把每行结尾的换行符也读进来,不加处理的话,你的“空格”数量里可能混入了换行。我习惯先用rstrip('\n')或者直接去掉首尾空白,再逐字符判断。

还有一类统计题会要求区分“英文字母”“数字”“空格”“其他”。判断函数如下:

if ch.isalpha(): letters += 1 elif ch.isdigit(): digits += 1 elif ch.isspace(): spaces += 1 else: others += 1

需要留意的是isalpha()对中文也会返回True,对中文输入友好,但OJ的字符统计题一般不需要考虑中文,所以没问题。也有题目专门让你统计“大写字母”和“小写字母”,那就要用isupper()和islower()分开判断。

4.3 三角形面积与格式化输出:精度控制在OJ题里的分量

三角形的面积计算是个经典的数学应用题。最常见的是海伦公式,已知三边长a、b、c,半周长p = (a + b + c) / 2,面积S = √(p × (p - a) × (p - b) × (p - c))。

代码本身很简单:

import math a, b, c = map(float, input().split()) p = (a + b + c) / 2 s = math.sqrt(p * (p - a) * (p - b) * (p - c)) print(f"{s:.2f}")

这个题有一个比较隐蔽的坑:如果输入的三条边构不成三角形,比如1、1、3,那么p - c是负数,乘积为负,math.sqrt会直接报错。题目如果没说输入一定合法,你就得先判断是否满足三角形不等式:

if a + b > c and a + b > c and a + b > c: ... else: ...

另外,三角形面积经常要求保留2位小数,f"{s:.2f}"是最稳妥的方式。不要用print(round(s, 2)),原因是round返回浮点数,浮点数显示规则会导致1.10显示成1.1,在OJ的逐字符比对里直接判错。

这类“样例全过但提交0分”的情况,九成都是格式化问题。我这里单独列一个排查顺序:先对照题目里的“输出样例”看“空格”和“换行”,再用格式化字符串统一处理浮点数,最后再考虑算法正确性。

4.4 数据交换、温度转换、整数四则运算:送的题也要会读题

2.6到2.9这几道题,难度不大,但特别容易因为“读题不细”而翻车。

数据交换题,一般要求不能用中间变量交换两个整数。其实Python里一行a, b = b, a就搞定了,但你要是真按“不能用中间变量”这个要求去想,反而会写得复杂。

温度转换题,无非是华氏度和摄氏度之间的公式换算:

F = C * 9 / 5 + 32 C = (F - 32) * 5 / 9

这类题真正的坑是:题目描述的输入输出格式可能不是“你输入一个数,程序输出一个数”,而是要求一次输入多组数据,或者输出带单位、带文字的提示。比如“输出格式:摄氏温度为xx,华氏温度为xx”,那就意味着你的输出必须带上“摄氏温度为”这几个字,一个标点都不能错。很多第一次刷OJ的人就在这里丢分:程序逻辑全对,输出文字格式和题目要求不一致,被判格式错误。

整数四则运算题就更基础了,主要考验的是能不能分清整数除法和浮点数除法。Python 3里/是浮点除,//是整数除。如果题目要求“输出商和余数”,就必须用//和%;如果题目要求“结果保留两位小数”,就用/再格式化。

5. 我在本地搭的“判题模拟器”:如何用几行脚本提前避开判题平台的坑

5.1 为什么样例过了,提交还是0分

刷了几道题之后,我发现一个非常普遍的现象:很多人在本地IDE里测试样例,输出和题目样例一模一样,可一提交就是Wrong Answer。问题到底出在哪?

其实OJ平台的判题逻辑很简单:平台会准备多组测试数据,包括常规样例、边界值、极端值。你的程序必须对所有测试数据都输出正确结果,才算通过。本地测样例过了,只是代表你能处理“题目展示的那一组输入”,不代表你能处理其他隐藏测试。

最容易挂掉的测试数据类型如下:

  1. 边界值:比如分段函数的临界点,成绩刚好90分、电费刚好50度。
  2. 极端值:非常大的整数、非常小的小数、0值、负数。
  3. 输入格式变体:多组数据、带多余空行、末尾换行符。

5.2 一个简单的本地批量测试脚本

我后来想了办法,写了一个极其简陋但非常实用的“本地判题模拟器”。思路很简单:把测试用例和期望输出存成一个个文本文件,然后用脚本批量跑你的程序,比对输出是否完全一致。大概长这样:

import subprocess import sys import os cases_dir = "test_cases" # 每个测试用例存成 input_i.txt 和 output_i.txt program = "solution.py" # 你的题解 for i in range(1, 15): input_file = os.path.join(cases_dir, f"input_{i}.txt") output_file = os.path.join(cases_dir, f"output_{i}.txt") with open(input_file, "r") as f: test_input = f.read() with open(output_file, "r") as f: expected = f.read().strip() result = subprocess.run( [sys.executable, program], input=test_input, capture_output=True, text=True ) actual = result.stdout.strip() if actual == expected: print(f"case {i}: passed") else: print(f"case {i}: failed") print("expected:", repr(expected)) print("actual :", repr(actual))

这个脚本的核心价值在于:它让你在提交之前就暴露“多一个空格”“少一个换行”“边界值处理错误”这类问题。我当时就是靠这个脚本,在一次提交之前提前发现了2.1分段函数中x=0这个边界的错误。

如果你不想写文件,也可以直接在本地写一个简单的验证函数,把输入输出都放在内存里比对。总之,一切以“输出的字符串和预期完全一致”为准。

5.3 常见扣分点自查清单

结合第二章题目,我总结了一张自查清单,每次提交前对照着过一遍,能省很多冤枉分:

扣分点具体表现处理方法
输出格式多余空格、缺少换行、用了全角字符严格按题目输出样例比对
浮点精度直接print导致尾数误差一律用f-string格式化
边界条件临界点被漏掉或重复计费用数轴标出所有区间断点
输入读取input()只读了一行,漏掉后续输入确认题目是单行输入还是多行输入
字符判断isalpha()误判、未处理换行符先清洗数据,再逐字符判断
除零/根号负数未考虑非法输入按题目要求或主动加合法性判断

这份清单看起来很朴素,但刷题时对照它逐项检查,能有效降低“逻辑正确但被判错”的概率。尤其是格式问题,它不考验你的算法水平,纯粹考验你读题够不够细。

6. 刷完这14题之后,我对入门刷题的几点实在建议

6.1 不要只刷“会”的题,要刷“卡住”的题

第二章的14道题,难度梯度设计得很有讲究:前面几道简单到让你放松警惕,中间几道开始要求你理解迭代和边界,后面几道则需要组合语法知识。我见过太多人只刷自己一眼就会的题,刷完感觉良好,到考试或实际项目里还是不会。

我自己刷题的原则是:一道题如果5分钟内没思路,先看题解,理解思路,然后关掉题解自己重新写一遍。如果一道题能轻松做出来,也要问自己三个问题:

  1. 我有没有处理边界值?
  2. 输出格式和题目要求是否完全一致?
  3. 如果我换一种输入规模,代码还能不能跑?

6.2 代码会写和代码能过是两回事:把读题当成需求分析

我在本地“判题模拟器”上吃了足够多的亏之后,才真正理解了一个道理:OJ编程题本质上是一个极简版的需求分析题。题目描述是你拿到的需求文档,判题系统的测试用例是验收标准,而你的代码是交付物。

你做需求分析时会问“用户到底要什么”,做OJ题目时要问“题目到底要求输出什么”。是输出数字还是带单位的文本?是一行结果还是多行结果?是保留两位小数还是保留六位?这些细节全部决定了你是否能通过验收。

我建议每次读题的时候,把关键要求用笔圈出来:输入格式、输出格式、边界条件、公式参数。这样做可以减少八成以上的低级错误。

6.3 算法不是刷出来的,是“改”出来的

最后聊一点感想。很多人以为“刷题多=算法好”,但我的体验恰恰相反。刷题的价值不在于数量,而在于“改”的过程——把一段能跑的代码改成符合题目要求的代码,把一种笨方法改成更高效的方法,把一种写错的边界改成正确的边界。这个过程才是真正的学习。

第二章2.1~2.14这14道题,每一道都有至少两种解法,有至少一个容易踩的坑。如果你刷的时候只是把样例复制过去跑通就收工,那这14道题对你的帮助非常有限。但如果你愿意在每道题上多花10分钟,想一想边界条件、想一想输出格式、想一想有没有更好的写法,那这14道题的基础价值,绝对比得上后面某些看起来很难的章节。

我自己记录的刷题笔记里,第二章这几道题占据的篇幅比后面很多章节都多。无他,就是因为在这里踩过的坑、悟出的道理,奠定了我后续刷一切OJ题的基本功。所以如果你也正卡在这一章,慢一点没关系,把每个坑都踩明白,后面自然就顺了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询