news 2026/10/7 4:59:01

Python刷题进阶:董付国编程题41-50解题思路与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python刷题进阶:董付国编程题41-50解题思路与避坑指南

我最初刷董付国老师Python小屋编程题的时候,前40题给我的感觉是:语法点很密集,但每一题基本都能在十几行内收工。到了41-50这一段,情况明显变了。题干变长,输入输出样例开始变得刁钻,需要自己判断的情况也多了起来。这正好卡在“能看懂别人的代码”和“能独立写出完整可运行代码”之间,很多人就是在这里弃坑的。这篇文章我不打算一份一份贴标准答案——Python小屋的题号在不同渠道会有微调,死记答案没有意义——而是把41-50当做一个典型的训练切片,聊聊这一段公认的考察重点、我刷题时的完整思路,以及那些最不起眼却最容易扣分的细节。正在刷这套题、刚学到函数和列表这块的朋友,可以参考。

1. 从第41题开始,题目在悄悄变难:先搞清楚它想考什么

1.1 这个阶段不再是“单点语法”训练

前40题通常很纯粹:学了if就练if,学了for就练for,你只要把当天学的语法套上去,基本能过。但41-50明显不是这个套路了。它默认你已经有了一定的语法基础,然后开始把多个知识点揉在一起考。我在刷这一段的时候,明显感觉到题目不再是“让你展示某个语法怎么写”,而是“让你用已知的语法去解决一个具体问题”。

从Python小屋这套练习的常见编排来看,41-50这一段的高频考察方向大概集中在下面几个:

考察方向常见出题形式读者容易忽略的点
字符串与列表结合处理对一句话做字符分类、单词统计split之后残留换行符或空字符串
循环嵌套与二维结构矩阵转置、打印图案、行列统计内层循环变量没有重置
函数定义与调用把一段逻辑封装成函数再调用局部变量和全局变量的作用域
输入输出格式控制多行输出、定宽对齐、空格分隔末尾多了一个空格、多了一个换行
基础算法雏形去重、排序、统计频次、求最大最小值是否保持了原有顺序

为什么题目会这么设计?因为真正写程序的时候,没有人会告诉你“这里用for循环”“那里用列表推导式”。你面对的是一段需求描述,你需要自己判断用什么东西去实现。41-50就是在逼迫你完成这个转变:从“学语法”切换到“做需求”。

1.2 难度不是突然升高,而是“题干密度”增加了

我第一次打开41-50时,第一反应是:题目怎么变长了?仔细读两遍发现,背后的语法并没有超纲,还是if、for、列表、字典、函数这些基础东西。真正变的是信息密度:一道题里同时给出了多个条件,每个条件都可能在输出结果里占一席之地。

举一个非常典型的例子:统计一段英文文本中“出现次数最多的字符”。听起来很简单,但题目可能同时要求忽略大小写、不统计空格和标点、如果有多个字符并列最多要全部输出,而且输出还要按字母顺序排列。这种题如果漏看一个条件,代码能跑,答案却不对。

所以刷这一段的题,第一步不是打开编辑器,而是先读题、划条件。我个人的习惯是拿张纸把题目里的所有约束列出来:输入是什么,输出是什么,有哪些特殊情况。先把这些搞清楚,后面写代码才不会东补一块西补一块。

2. 动手之前,先把输入输出关系在纸上跑通

2.1 手工用例法:三组用例比十行代码更值钱

很多刷题的人有个毛病:拿到题目立刻写代码,写完一运行发现输出不对,再回头读题,再改,如此反复。这种方式特别浪费时间,而且改着改着容易把逻辑改乱。我现在的做法是:不管题目多简单,先手动构造三组用例,在注释或者纸上写出“输入→预期输出”的关系,然后再考虑代码。

三组用例通常是:

  1. 题目本身就给出的样例,这是最基础的要求。
  2. 一组最普通、没有任何特殊情况的随机数据,用来验证主逻辑。
  3. 一组最刁钻的边界数据,比如空字符串、单个字符、全部字符相同、最大输入等。

拿一个字符串字符分类统计的题目来演示。假设输入是:

Hello, Python!

我预期输出是:

字母:11 数字:0 空格:1 其他:2

H、e、l、l、o、P、y、t、h、o、n一共11个字母,中间一个空格,逗号和感叹号算其他,数字为0。这个过程不需要Python,口算都能完成。它最大的作用是在你写代码之前,先建立一个“正确答案长什么样”的参照物。代码跑完以后,拿这个参照物一对比,逻辑对不对立刻知道。

2.2 input()读取的三类常见情况

41-50这批题里,输入读取方式的坑比很多人想象的要多。我把常见的几种情况整理出来:

第一,只有一行输入,而且是一段可能包含空格的字符串。直接用input().strip()。很多人会漏掉strip,结果输入行末尾的换行符被带进数据里,统计字符时莫名其妙多出来一个\n,排查半天。

第二,第一行给一个整数n,后面有n行数据。写法是:

n = int(input()) data = [input().strip() for _ in range(n)]

这里要特别留意,input()默认读的是字符串,如果后面要转成整数,需要逐个转换。

第三,不知道输入有几行,需要一直读到文件结束。这是最容易被忽视的,很多题目表面上没写“输入到EOF”,实际上测试数据是多行的。这时要用EOFError捕获:

while True: try: line = input() except EOFError: break

别问我为什么单独说这个,我在第41题附近就吃过亏:只处理了一行输入,结果测试数据里有三行,后面两行直接没用上。

2.3 输出格式是看不见的扣分点

逻辑对了,格式错了,照样拿不到满分。这是编程题特别残酷的地方。我见过太多人写出来的结果明明值是对的,却因为多了一个空格或者多打了一个换行被扣分。

常见的输出要求有这么几种:

  • 每行一个结果。这种最简单,直接print,因为print自带换行。
  • 同一行的结果用空格分隔,末尾不能有多余空格。这种最容易错,用循环加print会在每个数字后面都带一个空格。正确做法是先把结果转换成字符串,再用join连接:
nums = [1, 2, 3, 4, 5] print(" ".join(map(str, nums)))
  • 结果需要定宽对齐,比如右对齐、保留两位小数。用f-string最省事:
print(f"{score:>5}") print(f"{price:.2f}")

写代码之前先看看样例输出长什么样,是单行还是多行,每个字段之间是空格还是逗号,末尾有没有多余空白。这个习惯能帮你避开大量无意义的返工。

3. 刷41-50时最容易翻车的四个隐藏考点

3.1 在for循环里修改正在遍历的列表

这个坑我在刚刷题时踩得特别惨。题目要求“去除列表中的重复元素并保持原有顺序”,我一开始图省事,直接在原列表上做remove:

lst = [1, 2, 1, 3, 2] for x in lst: if lst.count(x) > 1: lst.remove(x)

结果输出根本不是预期。原因很简单,for循环遍历列表的时候,是根据索引往下一个位置走的。remove掉一个元素后,整个列表会往前缩,原来的“下一个元素”跑到了当前位置,循环就把它跳过去了。这种问题最恶心的地方是:代码不报错,结果就是不对。

正确做法是创建一个新列表来收集结果:

seen = set() res = [] for x in lst: if x not in seen: seen.add(x) res.append(x)

如果只是去重且不要求保持顺序,直接list(set(lst))也行。但41-50这类题常常要求“保持原来顺序”,那就必须用上面这种写法。

3.2 函数内修改全局变量:要主动声明

到了41-50,题目开始要求把功能封装成函数。这一下子就把作用域问题带出来了。最常见的错误是:

total = 0 def add(): total += 1

运行直接报UnboundLocalError。原因在于Python有一条规则:如果在函数内部对某个变量进行了赋值操作,Python会默认它是局部变量。total += 1相当于先读取total再赋值,可此时total还没有被声明为局部变量,于是报错。

解决方式有两种。一种是使用global声明:

total = 0 def add(): global total total += 1

另一种是更推荐的写法:尽量避免在函数内部修改全局变量,而是把数据作为参数传进去,把结果用return返回。这样函数就变成了一个“输入数据→输出结果”的黑箱,可读性和可测试性都高很多。

这里还有一个容易混淆的细节:如果函数内部是对列表进行append、修改列表元素,比如lst.append(x),不需要global,也不需要返回新列表,因为列表本身是可变对象,修改的是对象内容。但如果是对变量重新赋值,比如lst = lst + [x],那就需要看作用域了。搞清楚“可变对象原地修改”和“变量重新绑定”的区别,这个阶段会顺畅很多。

3.3 整数除法和浮点误差

编程题41-50里经常出现“逐位处理数字”的题,比如统计一个整数里每个数字出现的次数、求水仙花数、判断回文数。这类题有一个稳妥的技巧:先把整数转成字符串处理。

我见过很多人在统计数字出现次数时写这样的代码:

while n > 0: digit = n % 10 ... n //= 10

思路没错,但处理数字0的时候特别容易出问题。比如统计100这个数,如果你先n %= 10去逐位剥离,中间会莫名其妙丢一个0。直接str(n)就没这么多事:

for ch in str(n): ...

再说浮点误差。Python里0.1 + 0.2不等于0.3,这个经典问题在涉及金额或精确小数统计的题目里会突然冒出来。如果你用round(x, 2)去做四舍五入,看似没问题,其实round用的是银行家舍入规则,而且浮点数本身的误差可能导致舍入结果和预期不一致。遇到“保留两位小数”的要求,最稳的是用f-string格式化输出,或者直接用Decimal。如果题目涉及金额,更推荐的做法是内部统一用“分”作为单位,全部用整数运算,只在最后输出的地方格式化。

3.4 排序时“你以为的规则”和“Python的规则”不一致

排序题在41-50里经常出现,而且通常不是单纯排个序那么简单。一个容易被忽略的规则是:sorted对数字是按大小排,对字符串是按字符的Unicode码位排。这意味着字符串“10”会排在“9”前面,因为先比较第一个字符“1”和“9”,而“1”的码位比“9”小。如果你以为字典序等于数值序,这里就会翻车。

另一种情况是要按自定义规则排序。比如有一组元组[(name, score)],要求按score从高到低排序。很多人会写:

sorted(data, reverse=True)

结果却是先按name排,再按score排,因为元组默认先比较第一个元素。正确写法是:

sorted(data, key=lambda x: x[1], reverse=True)

还有一个和排序强相关的问题:字典的遍历顺序。Python 3.7以后,字典默认按插入顺序遍历。这本身没问题,但如果你希望输出按字母顺序排序,不要依赖插入顺序,直接遍历sorted(data.items())。

4. 做编程题的正确姿势:一套可复用的解题骨架

4.1 三段式程序结构

41-50开始出现“完整解题”的味道了:有输入、有核心逻辑、有输出,已经不是一道题只要写一个函数那么简单。我后来总结出一套三段式结构,几乎能套在所有类似题目上:

def solve(param): # 核心逻辑,返回结果 return result def main(): # 1. 输入处理 # 2. 调用 solve # 3. 输出处理 if __name__ == "__main__": main()

这样做的好处有三个。第一,核心逻辑被隔离在solve函数里,调试的时候不用每次都在整个程序里翻找。第二,输入处理和输出处理相对固定,真正会变化的部分被独立出来。第三,如果题目要求同一段逻辑处理多个测试数据,直接循环调用solve就行。

我在刷41-50时,很多题一开始是全部堆在main里写,写到一半发现逻辑绕成一团。后来强制自己先拆函数再填代码,清晰度立刻上了一个档次。

4.2 完整走一遍:统计一个整数中每位数字出现的次数

用一道典型的题来演示完整流程。输入一个整数,输出每一位数字出现的次数,按数字升序排列。输入122333,预期输出:

1:1 2:2 3:3

先手工推演:1出现1次,2出现2次,3出现3次。然后写代码:

def digit_counts(s): counts = {} for ch in s: counts[ch] = counts.get(ch, 0) + 1 return counts def main(): s = input().strip() counts = digit_counts(s) for d in sorted(counts): print(f"{d}:{counts[d]}") if __name__ == "__main__": main()

这里用get(ch, 0)而不是先判断再赋值,可以少写三行。用str而不是逐位取余,避免数字中包含0时出现的各种边界问题。最后用sorted(counts)保证输出按数字升序排列。这个模式在41-50里很通用:字符串处理、字典计数、排序输出,三步走完。

4.3 从“能运行”到“像标准答案”的三步

很多人的代码能跑,但看起来乱,而且换一个输入就崩。我觉得一个程序写完以后,至少要再做三件事:

第一,变量命名要有意义。a、b、c这种命名在短代码里还能忍,题目一复杂就完全看不懂了。用count、seen、result、data这类有语义的名字,调试成本会低很多。

第二,核心逻辑必须封装成函数。哪怕题目没要求,我也建议你这么做。我之前刷题时有一道题写了三遍,每次都是全部逻辑堆在main里,改一个地方全改。后来把核心逻辑抽出来,几十行代码改成一个小函数,后续调整只在函数内部做,方便多了。

第三,能用现成方法的地方尽量用,但不要为了短而短。比如用join代替循环拼接、用dict.get代替if判断、用列表推导式代替临时列表加循环。这些写法能让代码更紧凑,但前提是你读得懂。如果一行推导式写出来自己都要想半天,那不如写常规循环。

这里特别提醒一个输出细节:在答题环境里,程序输出不能带任何多余的提示文字。经常看到有人写完代码,为了“友好”加了句print("请输入:"),结果这份输出直接被判定为错误。例题里需要你输出什么,你就只输出什么。

5. 刷完41-50之后的复盘,比再做五十题更有用

5.1 每组题至少补三组边界测试

代码写完后不要急着看下一题,先对现在的程序做一轮边界测试。我总结过一个简单的检查表,每次刷到一批题都会过一遍:

边界情况典型后果对应的防护习惯
空输入程序直接报错,或逻辑跑不出结果读入后先判断是否为空字符串
单个字符循环次数异常、索引越界从长度为1的输入开始推演
全部相同字符去重、统计逻辑出现偏差确认去重后是否只剩一个
最大规模输入程序运行超时检查循环嵌套,考虑优化复杂度
带前导或末尾空格统计结果偏多输入统一做strip

我在刷41-50时,有一道统计单词数量的题,程序在普通用例上跑得好好的,直到我用了一个开头带空格的字符串,统计结果多出1个空单词。原因就是split之后列表里混入了空串。后来我在处理字符串时,先是strip,再用split,这个问题就再也没出现。

另外,这一阶段的题虽然不至于涉及太复杂的算法,但如果你用了双层循环遍历一个上万规模的数据,运行时间就会明显变长。遇到这类情况,可以想一想能不能用字典、集合把内层循环换成O(1)的查找。这算是为后面学算法打底子的习惯。

5.2 错题本记“为什么会错”,不是抄答案

错题本这个东西,很多人记成了“答案抄写本”。把正确代码一贴,回头根本不看。我觉得真正有用的是记“错误链路”。

我自己的记录格式是四条:

  • 题目涉及的知识点,比如“字典计数”“字符串去重”。
  • 我当时的错误输出或者报错信息。
  • 根因,一句话说明为什么错。
  • 正确的写法或思路。

举个例子,我曾经有一道题要求输出多个结果,每个结果用空格分隔。我当时图省事,写了个循环一个个print,结果每个结果换了一行,格式分全扣。我的错题本上就记着:根因是“print默认end='\n',题目要求end=' '或用join拼接”。以后凡遇到输出格式的题,我会先看一眼样例末尾是换行还是空格。

错题本是给自己看的,不用写得多漂亮,但一定要能还原当时的思考过程。过一周之后再翻,如果你还能看懂当时为什么错,那这个错题本才算没白记。

5.3 把十道题变成自己的函数弹药库

刷完41-50之后,如果你只是把每一道题的代码跑通然后关掉,那这批题的价值基本上只发挥了一半。我会建议做一步额外的动作:从每题里提炼出一个以后能复用的函数,存到自己的工具包里。

比如这批题练过的典型函数可能有:

def unique_keep_order(lst): seen = set() res = [] for x in lst: if x not in seen: seen.add(x) res.append(x) return res def char_counter(s): counts = {} for ch in s: counts[ch] = counts.get(ch, 0) + 1 return counts

以后在别的地方遇到“去重但保持顺序”“统计字符频率”,直接把这些函数拿过来用,不需要重新思考一遍。这个阶段真正的收获,不是十道题的答案,而是你从这十道题里抽象出来的通用能力。

我自己现在写程序,遇到稍微复杂的文本统计,还是会想起当年从41-50里提炼出的那批小函数。它们不一定有多高明,但每一次调用都让我觉得那段“卡在理解与上手之间”的刷题时间没有白费。编程题的价值从来不在题目本身,而在于你做完之后沉淀下来的那套思路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 4:56:03

Go并发编程:RWMutex读写锁原理、实战与避坑指南

在Go的并发编程里,RWMutex是个绕不开的名字。做高并发IM、写缓存服务、处理在线状态同步,这类读多写少的场景,你几乎每天都要和它打交道。很多朋友从Mutex直接切到RWMutex,以为只是把Lock换成RLock,结果线上出了死锁、…

作者头像 李华
网站建设 2026/10/7 4:55:39

AI驱动浏览器自动化:Cursor+Playwright实战自动发布文章

最近用 Cursor 配合 Playwright 做了一件挺有意思的事:让 AI 自己操作浏览器,登录头条号后台,填标题、写正文、点发布,一篇头条文章就这么自动发出去了。这个组合比我预期中要顺——Cursor 负责把自然语言变成可执行的 Playwright…

作者头像 李华
网站建设 2026/10/7 4:55:27

Claude免费共享账户真相与Claude Code配置避坑指南

有没有发现一个现象:现在很多技术交流群里,隔三差五就有人冒出来问一句“谁有免费的Claude账号借一下”,接着就是“同求”“蹲一个”,再往下就是各种拼车群链接。Claude火起来之后,连带着“免费共享账户”都成了一片江…

作者头像 李华
网站建设 2026/10/7 4:55:12

布儒斯特角与超宽带不对称反射的COMSOL仿真全解析

做电磁仿真这几年,我越来越觉得COMSOL这类工具最值钱的用法,不是把复杂结构跑通,而是把一个简单物理概念反复“逼”到工程极限上去看它的边界在哪。这个项目就是一个典型:超宽带、布儒斯特角、不对称反射,三个词单拎出…

作者头像 李华
网站建设 2026/10/7 4:54:15

程序员AI协作实战:7个可落地的工作流切片

1. 这不是“被取代”,而是“新工位”的入场券最近在三个不同城市的线下技术沙龙里,我都听到同一个问题被反复抛出来:“AI写代码这么快,我是不是该转行了?”问的人有刚毕业两年的前端,也有带团队十年的后端架…

作者头像 李华
网站建设 2026/10/7 4:53:04

电商AI客服60秒响应实战:从意图识别到动作闭环

1. 为什么“第一分钟”成了电商客服的生死线我去年接手一个中型服饰品牌的AI客服落地项目,目标很朴素:把人工客服从重复咨询里解放出来,让她们专注处理高价值客诉和复购引导。上线前团队信心满满——我们用了行业头部NLP引擎,训练…

作者头像 李华