我整理PTA浙大版《Python 程序设计》AC解答汇总时,最想单独拎出来说一说的就是第六章。这一章的难度比前面几章明显上了一个台阶,很多同学前面顺风顺水,到这里突然开始反复提交、反复报错。如果你也正卡在这一章,别急着怀疑自己,大概率不是你代码逻辑写错了,而是你还没习惯在线判题跟本地跑脚本之间的那点差别。这篇文章不会把所有题目答案平铺一遍,那不解决问题;我会结合第六章最常见的一类题——函数题,把“做题前要想清楚什么”“哪些坑最容易踩”“踩了之后怎么排查”讲清楚。正在刷这套题的人可以参考,刚学完函数、想找点OJ题练手的人也能看懂。
1. 先认清第六章的题到底在考什么
1.1 章节定位与难度变化
浙大版教材前五章基本在讲语法基础、数据类型、流程控制。学完这些,你能看懂代码,能写点简单的小脚本,但代码的“组织方式”还没建立起来。到了第六章,重心明显从“读程序”转向“写程序”,核心就是函数。这一章在教材里看起来不难,概念也就那么几个:定义、调用、参数、返回、作用域。可一旦换到PTA的题集里,事情就变了。教材课后题只要你能在解释器里跑出结果,基本就过;但PTA的函数题会直接给你一个不完整的代码框架,要你在限定位置补全函数,而且判题程序会用多组测试数据来验证你的函数行为。
这就会引出第一类困惑:为什么我本地写得好好的,一提交就满分不了?为什么我觉得自己算对了,判题就是非零返回?说到底,第六章不是考你会不会写“能运行的代码”,而是考你能不能写对“在任意合法输入下行为都正确的代码”。这两者的差距,恰恰是AC分数背后的真实含义。
1.2 高频考点分布
以我陆续提交的反馈来看,这一章的题目大体可以归成三类,每类的失分原因还不太一样。先列个表,后面逐个拆。
| 题型方向 | 常见考查方式 | 典型失分点 |
|---|---|---|
| 基础函数定义 | 给出主程序调用方式,要求补全函数体 | return和print混淆,函数没有返回值 |
| 数值递归 | 阶乘、斐波那契、最大公约数等经典递归 | 递归边界缺失、深度过大导致超时 |
| 数据处理函数 | 对字符串或列表做变换返回新结果 | 直接修改了传入的可变对象,产生副作用 |
这三种方向看起来简单,但每种背后都对应一个必须理解的机制。理解了机制,第六章的题目就不再是一道道孤立的题,而是一类通法。
2. 动手写代码前,先把这三个机制吃透
2.1 参数传递:传入列表时到底发生了什么
Python的参数传递语义,严格说是“传对象引用”。很多人以为把列表传给函数,就等于复制了一份列表,函数内部怎么改都不影响外面。这是第六章第一个大坑。看个最典型的例子:
def append_one(lst): lst.append(1) nums = [1, 2] append_one(nums) print(nums) # 实际输出 [1, 2, 1]函数内部对列表做append、sort、reverse这类原地操作,都会直接作用于原对象。在PTA的函数题里,这个特性有时候是题目故意考察的点:题目要求你“返回处理后的新列表”,如果你直接在原列表上操作,主程序后续再使用这个列表时,数据已经被污染,结果自然对不上。解决办法也简单,函数开头先复制一层:
def process(lst): temp = lst[:] # 复制一份,原列表不受影响 # 对 temp 做操作 return temp这里的底层原因是列表是可变对象,切片生成的是新列表,两者指向不同内存区域。理解到这一层,类似问题就不再靠背题了。
2.2 作用域:函数内赋值不等于修改全局变量
第二个高频问题出在作用域上。Python在函数内部给一个变量赋值,默认创建的是局部变量,哪怕函数外部有一个同名的全局变量。最常见的就是自增计数器这种题目:
count = 0 def inc(): count += 1 # UnboundLocalError这段代码一跑就报错,原因是函数内部看到了count的赋值操作,于是把count当作局部变量,但此时局部变量还没初始化,就直接做了加法。很多同学第一次遇到这个报错会懵,明明全局变量已经定义了。解决办法要么用global声明:
count = 0 def inc(): global count count += 1要么改用返回值的形式,避免修改全局变量。在OJ的封闭函数题里,最好是返回新值而不是依赖全局变量,因为判题程序可能会多次调用函数,全局变量状态一旦没复位,后面的测试点就会连环错。
2.3 return和print:这一步混淆直接决定AC还是WA
第六章函数题里最不值得丢的分,就是return和print用错。判题程序怎么知道你算对了?它看的是函数的返回值,不是屏幕输出。很多题目在主程序里已经写好了输出逻辑,比如主代码是print(solve(n)),那你的函数就必须返回结果;如果你在函数内部写了print,函数返回None,主程序打印出None,结果就是答案错误。反过来,如果题目要求“直接输出结果”,函数内部才需要print。
判断技巧特别简单:看题目的模板里有没有预留return,看主调用代码怎么使用你的函数。题目描述里写“返回xxx”,就return;写“输出xxx”,才print。这一条看着像常识,但我刷第六章时见到不止一次这种提交记录,本地一跑显示明明有结果,一交就WA,往往就是这个问题。
3. 第六章函数题的AC思路:三个高频题型拆解
3.1 空函数框架题的补全顺序
PTA的函数题经常给一个残缺模板,例如:
def solve(n): # 请在此处实现函数很多人的第一反应是“把函数体写完就行了”,但忽略了一个步骤:先看主程序怎么调用。主程序可能是:
n = int(input()) print(solve(n))也可能是:
n = int(input()) solve(n)同样是实现一个功能,前者要求返回结果,后者要求函数内部自己打印。这直接决定了函数体最后一句话是return还是print。正确顺序应该是:先读题目的输入输出样例,再读模板代码,确认函数是否需要有返回值,然后才动手写实现。这样能直接规避一大类“本地对、线上错”的情况。补全函数体的时候,还要注意模板里可能已经给出了参数名,别自己另写一套参数名,否则函数签名变了,判题调用你的函数时直接报参数不匹配。
3.2 递归题的边界条件与效率控制
第六章涉及递归的题目,比如斐波那契数列、阶乘、最大公约数。教材里大多用递归讲,但直接照搬递归实现去提交,会遇到两个坎。第一个坎是边界条件。很多同学能写出递归公式,但忘记写终止条件,或者终止条件写成了if n == 1而忽略了n == 0的情况。第二个坎是性能。以斐波那契为例,朴素的递归写法:
def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2)如果测试点里有 n=30、n=40 这类数据,运行时间会迅速拉长;如果 n 再大一点,还可能递归深度超限直接非零返回。原因很简单,这个递归会把同一个子问题反复算好多遍,复杂度接近 O(2^n),完全没必要。解法要么改成迭代:
def fib(n): a, b = 0, 1 for _ in range(n): a, b = b, a + b return a要么在递归基础上加记忆化,用一个字典把算过的结果存下来。PTA判题不会限制你只能用递归,只要算法正确、效率够、结果对,就是AC。所以遇到递归超时第一反应不是去调递归写法,而是考虑能不能换成迭代,这是性价比最高的改法。
3.3 lambda表达式:什么时候值得用
部分排序、映射类的函数题,可能会用到lambda。比如要求按字符串长度排序,最常见写法是sorted(words, key=lambda x: len(x)),简洁明了。但我想提醒一点:如果这个逻辑比较复杂,或者你需要复用,就别硬抽象成lambda。有一位同学跟我讨论题目时,非要把一个四行的逻辑塞进lambda里,写得极其难读,调试还麻烦。OJ只判结果,不看代码风格华丽程度,但你自己后续复习时要能看懂。我的建议是:能写普通函数说明白,就用普通函数;只有当逻辑简单到一句话能说清,才用lambda。这不影响AC,但能让你的解题过程更顺畅。
4. 实操过程:整理这套AC解答时我的完整做法
4.1 题目分类与归档规则
整理解答汇总不只是把AC代码堆在一起,那样过一个月你自己都懒得翻。我自己的做法是按章节主题建目录,再按题型拆成子目录。第六章的函数题,我会分成“基础函数”“递归专项”“列表/字符串处理”三个文件夹,每道题命名带上题目的功能关键词,比如“6-3-字符串反转函数”“6-9-斐波那契递归改迭代”。这样做的原因是复习时可以按题型集中刷,而不是按题号从头翻到尾。PTA的题目顺序本身不一定按难度排,但题型分类能让你集中攻克同一个薄弱点。
4.2 注释写什么才有价值
很多人的代码注释要么是零,要么全写废话,像# 定义函数这种注释等于没写。我整理AC答案时给自己定了几条规则:注释要说明意图,不描述动作;关键边界条件必须写清楚;一个地方如果容易踩坑,才值得写注释。比如:
def duplicate_removal(lst): # 题目要求返回新列表,不能修改原列表,因此先切片复制 result = [] seen = set() for item in lst: if item not in seen: seen.add(item) result.append(item) return result这段注释里最重要的一句话是“不能修改原列表”,下次复习时你一眼就能想起当时为什么这么写。否则过了两个月你看到这段代码,可能会疑惑为什么不直接在原列表上去重。
4.3 AC一次不等于真的会了
整理AC解答有个心理陷阱:提交通过那一刻,人就放松了,觉得这题已经掌握。我自己后来刷其他题时发现,隔一周再写同一题经常写不顺。后来养成一个习惯:每道题AC之后,先记录代码,然后合上所有参考答案,等几天后凭记忆重写一遍;能不看任何提示写出来并通过,才算真正掌握。这个习惯放到第六章尤其有效,因为这一章题量不小,题目之间的解法又有相似性,很容易混。比如今天写了斐波那契的矩阵快速幂版本,明天写爬楼梯,其实底层是同一套思路,能默写出来,才说明你理解的是通法而不是背下的具体代码。
5. 刷题现场:常见报错与排查实录
5.1 非零返回:先看报错类型再动手
第六章最容易把人吓住的报错就是“非零返回”。这个词看起来像系统错误,其实翻译过来就是程序运行时崩了。常见原因是递归没有终止条件导致栈溢出,或者访问了不存在的下标。遇到非零返回,我建议把提交页面的错误信息展开看。如果提示 RecursionError,基本可以确定是递归深度超限,优先检查边界条件;如果是 IndexError,就检查列表下标是否越界,尤其是空列表和只有一个元素的边界输入。排查时可以在本地构造这类边界输入,多打几个print判断程序执行到哪一步,定位速度会快很多。有一种情况容易忽略,就是我前面提到的函数内部副作用:函数里修改了传入列表,后续代码再访问这个列表时取到了被改过的值,运行过程不报错,但输出全乱。这种最难排查,所以写函数时开头先判断一下这份数据能不能改。
5.2 隐藏测试点:空值与边界值是重灾区
PTA的每个测试点都有设计意图,其中必有一两个藏在边界值上。第六章的函数题常见的隐藏测试包括:空字符串、长度为1的列表、n等于0或1、负整数、非数字字符。举个例子,实现一个统计字符串长度的函数,你写成for i in range(len(s))思路没问题,但如果某个测试点传入了空字符串,你的循环体一旦无条件访问s[0],就会直接越界。所以我写这类函数时,习惯在开头就把边界情况单独处理掉,宁可多写两行if,也不赌测试点里没有空值。这类细节不写进教材,但判题系统不会放过你。
5.3 浮点数比较引发的不稳定
第六章如果涉及除法或者开方,比如判断某数是不是完全平方数,AC率会明显低一截,因为浮点数比较有精度问题。最常见的是int(sqrt(n)) == sqrt(n),在n很大时,浮点数开方取整后再平方,可能不等于原数。稳妥做法是尽量避免浮点比较,用整数运算判断,比如计算整数平方根后检查平方是否等于原数。这种处理在第六章出现的频率不高,但一旦出现,很多人的本地样例都能过,隐藏大数测试点就会挂。我整理答案时看到这种解法,会在注释里特别标注“浮点数比较不可靠”,提醒自己以后看到类似题优先想整数做法。
6. 给正在刷这一章的人几点建议
6.1 别急着看别人的AC代码
在OJ上卡题时,人很容易想快速找一份AC代码抄上去。这个做法最大的问题是,第六章函数题的核心考点是代码的组织方式和函数行为,抄答案只能过一道题,下一道同类型题目换个外壳你可能还是不会。我整理这份AC解答汇总,本质上是为了给后来者提供一个参考的终点,而不是起点。卡题半小时之后看答案没问题,但看答案之前至少要自己写过一版,哪怕这版是错的,你的大脑才会对解法中的关键点有记忆;否则看答案就是看天书,关掉页面全忘光。
6.2 本地测样例时加一份“手写测试集”
PTA的公开样例通常只有一两组,提交前可以在本地把边界输入全部试一遍。比如函数接收的是正整数,至少测一遍1;接收的是字符串,至少测一遍空字符串;接收的是列表,至少测一遍空列表和单元素列表。此外还可以模拟主程序的调用方式,如果判题程序调用多次,你的函数是否能重复执行且结果一致。这套手写测试不需要很复杂,就是题目给的样例之外额外想几个极端值。实际操作时会发现,很多隐藏测试点就是在这些极端值上报错的,提前测出来能省不少提交次数。
6.3 把第六章当成前后知识的分水岭
第六章的内容衔接了前面的基础语法,也引出了后面更复杂的程序设计思想。如果这章的函数题刷得扎实,后面讲到模块、类、文件操作时,你会觉得很多坑似曾相识。举个例子,理解了可变对象的引用传递,后面接触类中修改对象属性就会更容易;理解了作用域,后面调试闭包也会少走弯路。反过来说,这一章如果只是背了几道题的答案混过去,后面的题目会反复绊你。我整理完全部AC解答后最深的体会是,OJ不会因为你理解原理就给分,它只看代码的行为是否正确;但只有理解了原理,你写的代码才能在各种测试点下行为都正确。刷完第六章以后再看前面的代码,会有一种“我当时怎么写得那么乱”的感觉,这就是进步的信号。
我在实际整理这套解答时还发现一个有意思的现象:同一道题,不同人有不同的AC写法,有的简短但难以理解,有的冗长但每一步都很清晰。对于学习和复习来说,清晰比聪明更重要。PTA的判题不会为“巧妙”加分,但你的未来复习会为“易读”省下大量时间。希望这份关于第六章的拆解能帮你少走几个来回,也希望你自己动手整理一份只属于你的AC解答汇总,那比看我这份有用得多。