
这十道题我前前后后刷过至少三遍每次写都有新收获。用一句话概括这个系列的价值它用最小的代码量把 Python 里最常用的循环、判断、列表操作、函数定义、日期处理全部串了一遍。如果你刚把 Python 语法过完一遍正愁“不知道写点什么练手”那这份题单就是为你准备的。哪怕你已经有工作经验回头再看老题也能在“怎么写更优雅”这件事上找到乐子。我一直觉得刷题的意义不在于“做出来”而在于“做出来之后还能怎么改”。这十道题有个共同特点入口极低出口极高。第一道题小学奥数水平就能读懂但真要做到代码健壮、风格清晰、边界考虑周全没有几十行代码的积累还真不行。1. 为什么这套经典题值得一刷再刷1.1 它几乎覆盖了 Python 入门的全部核心语法程序 1 到 10 这十道题从表面看是十个小练习实际上是把 Python 最基础也最常用的语法点全部练了一遍。循环、条件判断、列表切片、函数定义、递归调用、日期时间模块、math 模块、格式化输出……这些看起来零散的知识点通过一道道题目串起来之后你会发现它们彼此之间是有联系的。比如你写完程序 3 的完全平方数判断再去做程序 6 的斐波那契你对“用循环维护多个变量”这件事的理解就会完全不同。很多初学者容易陷入一个误区语法书翻了一遍又一遍视频课看了十几集真到写代码的时候还是无从下手。问题出在哪里缺练。编程是手工艺不是记忆类学科。你背得再熟“for 循环的语法是 for i in iterable”不亲手写上二三十遍遇到实际问题依然反应不过来。这也是我反复推荐这套题的根本原因它逼着你自己动手把语法变成肌肉记忆。1.2 我建议的刷题方式先做再看再改这套题的解析到处都是但我建议你先别急着看答案。拿到题以后哪怕写得又丑又长先用自己当下的水平硬写一遍。写不出来就去查语法查笔记查搜索引擎但别直接搜这题的答案。卡住一两个小时很正常这个过程本身就是学习。写完第一版之后再去看解析重点不是“我和标准答案差多少”而是“标准答案的思路和我有什么不同哪种更好好在哪”。这十道题我每道题都写了至少三种写法一种是最符合直觉的笨办法一种是利用 Python 语言特性的简洁写法还有一种是贴近工程实践的健壮写法。你能写出第一种说明语法过关了能理解第二种说明开始有 Pythonic 的意识了能领会第三种说明你已经在用工程师的思维想问题了。2. 程序 1 到 5从循环到分支扎实基本功2.1 程序 1数字组合与循环嵌套题目是这样的有 1、2、3、4 四个数字能组成多少个互不相同且无重复数字的三位数分别是多少这题第一反应就是三重循环。你把四层数字当作百位、十位、个位每个位置都从 1 到 4 遍历一遍然后用一个 if 判断三个位置互不相等把符合条件的组合打印出来就行。count 0 for i in range(1, 5): for j in range(1, 5): for k in range(1, 5): if i ! j and j ! k and i ! k: print(i * 100 j * 10 k) count 1 print(共有, count, 个)这里值得琢磨的是循环嵌套的执行顺序。外层 i 每取一个值内层的 j 就要完整跑一遍最内层的 k 更是要把所有可能都过一遍。三重循环共执行 4×4×464 次if 条件筛掉重复项之后剩下 24 个。这个“总次数 每层次数相乘”的思路后面学时间复杂度的时候会反复用到。不过说实话这种写法更像 C 语言的思考方式。Python 有更优雅的做法用 itertools.permutations 直接生成所有排列。permutations([1,2,3,4], 3) 第三个参数表示“从四个数里挑三个排列”出来的就是所有有序且互不重复的三元组。它跟你手写三重循环的结果完全一样但代码短了三分之二。from itertools import permutations count 0 for p in permutations([1, 2, 3, 4], 3): print(p[0] * 100 p[1] * 10 p[2]) count 1 print(共有, count, 个)两种写法都能跑但第二种明显更 Pythonic。我建议初学者两种都写一遍体会一下“标准库帮你省掉了哪些重复造轮子的工作”。以后你写排列组合相关的逻辑只要数据量不大直接用 permutations 基本不会出错。这里还有一个容易踩的坑很多初学者喜欢用列表推导式硬压缩代码比如[(i,j,k) for i in range(1,5) for j in range(1,5) for k in range(1,5) if i!j and j!k and i!k]。这种写法完全正确但嵌套三层列表推导的可读性真的很差。除非你有把握别人一眼能看懂否则我建议还是拆成普通循环。2.2 程序 2企业奖金与区间判断的艺术第二题是经典的经济管理类题目企业发放的奖金根据利润提成。利润 I 不超过 10 万元时奖金提成 10%超过 10 万到 20 万之间时高于 10 万的部分提成 7.5%20 万到 40 万之间高于 20 万的部分提成 5%40 万到 60 万之间高于 40 万的部分提成 3%60 万到 100 万之间高于 60 万的部分提成 1.5%超过 100 万的部分提成 1%。从键盘输入当月利润求应发放奖金总数。这种题目考察的是区间判断和分段计算。最直观的写法就是用一堆 if...elif...把每个区间分别列出来逐段计算。但初学者最容易犯的错误是什么区间边界重叠或者漏掉边界。比如写了I 100000的分支又写I 200000的分支如果把顺序搞反了后面的分支永远执行不到。我推荐一种更不容易出错的写法先从最高档往低档扣或者先把每一档的区间上限和对应提成比例存成两张表再用循环累加。第二种做法的好处是以后提成比例改了你只需要改表里的数字不用改代码逻辑。profit int(input(请输入当月利润元)) # 按提成区间切分的边界值从高到低 thresholds [1000000, 600000, 400000, 200000, 100000, 0] rates [0.01, 0.015, 0.03, 0.05, 0.075, 0.10] bonus 0 for i in range(len(thresholds)): if profit thresholds[i]: bonus (profit - thresholds[i]) * rates[i] profit thresholds[i] print(应发放奖金总数为, bonus, 元)这段代码的核心思路是“每次只计算超出当前档位的部分然后把利润砍到当前档位的上限继续往下算”。举个例子利润是 75 万第一次大于 60 万就把高出 60 万的部分 15 万按 1.5% 算之后 profit 变成 60 万再循环时 60 万不大于 60 万跳过继续往后匹配 40 万档。这样写一遍你还能顺便加深对“分段函数”的理解——这种分段计算在以后的计费系统、物流快递费计算里到处都是。另外这道题还涉及到一个 Python 细节input 返回的是字符串必须用 int() 或 float() 转换。我见过不少初学者漏掉转换直接拿字符串和数字比较报错之后一脸懵。如果你是在 Python 3 里跑字符串和 int 比较会直接抛 TypeError这个错误信息其实非常明确看到之后先检查类型转换。2.3 程序 3完全平方数与数学直觉第三题一个整数它加上 100 后是一个完全平方数再加上 168 又是一个完全平方数请问该数是多少这道题的常规思路是设这个数为 x则 x 100 n²x 100 168 m²两式相减得到 m² - n² 168也就是 (m n)(m - n) 168。然后枚举整数因子求出所有可能的 m、n再反推 x。但初学者看到题往往想不到代数变换直接用暴力枚举设一个范围逐个检查 x 100 的平方根是不是整数以及 x 268 的平方根是不是整数。这种枚举法在数学上不够漂亮但不失为一种可行的工程方案因为计算机速度快枚举个几百万次毫无压力。import math results [] for x in range(-100, 10000): a int(math.isqrt(x 100)) b int(math.isqrt(x 268)) if a * a x 100 and b * b x 268: results.append(x) print(results)这里我要特别提醒一个细节判断一个数是不是完全平方数不要用math.sqrt()然后判断小数部分是否为零因为浮点数精度问题可能会误判。老老实实用math.isqrt()它返回整数平方根再平方回来看是否等于原数。这个细节在 LeetCode 上专门有题考查过属于典型的“看着简单实际容易错”的地方。暴力枚举的 x 范围怎么定题目没有给范围我这边从 -100 开始因为 x 100 最小只能是 0如果 x -100x 100 为负数平方根就不存在了上限取 10000 是拍脑袋定的实际上如果你先用代数方法解出来你会发现符合条件的 x 数量非常少。写出枚举法之后再用因数分解法做一遍你会对“数学优化”四个字有更深的理解。2.4 程序 4日期判断与闰年的坑第四题输入某年某月某日判断这一天是这一年的第几天。这是十道题里最“工程化”的一道因为它涉及闰年的判断规则。闰年规则是能被 4 整除但不能被 100 整除或者能被 400 整除。很多初学者只记得“四年一闰”忘了“百年不闰四百年再闰”后半句直接在 1900 年这种整百年份上翻车。实现思路很简单用一个列表存 1 到 12 月每个月的天数二月份先按 28 天处理如果判断出是闰年就把 2 月改成 29 天。然后把输入月份之前的所有月份天数加起来再加上当前月份的日数。year int(input(请输入年份)) month int(input(请输入月份)) day int(input(请输入日期)) days_of_month [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if (year % 4 0 and year % 100 ! 0) or (year % 400 0): days_of_month[1] 29 total sum(days_of_month[:month - 1]) day print(f{year}年{month}月{day}日是这一年的第 {total} 天)注意这里用了切片days_of_month[:month-1]它取出的是索引 0 到 month-2 的元素正好是当前月份之前的所有月份天数。sum 函数一步到位比手写循环省事多了。用列表存天数的好处是扩展性极强——以后想加农历判断或者处理跨年替换数据源就行逻辑不用大改。这道题还有个变体考法给定一年的第 n 天反推它是几月几号。思路跟这个相反循环逐月减去天数减到不够减的那个月就是答案。建议你把两个方向的题都写一遍双向练兵的效果比单刷好得多。2.5 程序 5三个整数排序三种思路第五题是入门级的排序题输入三个整数 x、y、z请把这三个数由小到大输出。看到这道题你可能会觉得太简单了直接用 sorted() 就行。但它的价值恰恰在于你可以借此体会“排序”这个动作从手动比较到调用内置函数的演变过程。我建议初学者至少掌握三种写法。第一种是两两比较。三个数排序的核心是先保证前两个有序再把第三个数插入到合适位置。写起来就是一组 if 嵌套。第二种是借助列表的 sort 方法或 sorted 函数代码极短。第三种是手写“交换法”就是依次找到最小值放到最前面再找第二小的放中间。# 写法一两两比较最笨但最直观 x int(input(请输入x)) y int(input(请输入y)) z int(input(请输入z)) if x y: x, y y, x if x z: x, z z, x if y z: y, z z, y print(x, y, z)# 写法二利用内置函数最 Pythonic nums [int(input(请输入一个整数)) for _ in range(3)] nums.sort() print(nums)我特别想强调写法二里那个列表推导式。[int(input(...)) for _ in range(3)]一行完成了“执行三次输入并转型成列表”的工作这在以后处理批量输入时非常常用。有些读者可能觉得这样写有点炫技但 Python 社区普遍接受这种写法而且它有实实在在的好处如果你想扩展成输入 10 个数只需要把 range(3) 改成 range(10)其他代码一行都不用动。还有一个容易忽略的细节x, y y, x在 Python 里是一行完成交换的根本不需要临时变量 temp。这个语法糖背后的机制是元组的打包和解包底层确实创建了临时对象但 Python 帮你包好了。很多从 C 或 Java 转过来的程序员一开始不习惯用多了就会发现它实在太方便了。3. 程序 6 到 10算法原型与库函数初体验3.1 程序 6斐波那契数列从递归到生成器第六题是斐波那契数列输出指定个数的斐波那契数列。这道题在经典 100 题里的地位极高几乎每个初学者都会遇到。它最常用的两种实现是递推和递归而这两种方式恰好对应了“自底向上”和“自顶向下”两种完全不同的思维方式。递归写法非常符合数学定义F(1)1F(2)1F(n)F(n-1)F(n-2)。代码看起来简洁优雅但性能一塌糊涂。当你递归调用到 F(40) 的时候整个函数会被重复计算无数次时间复杂度高达 O(2^n)。我实测过F(40) 就要跑好几秒F(50) 直接等到怀疑人生。def fib_recursive(n): if n 2: return 1 return fib_recursive(n - 1) fib_recursive(n - 2)递推写法就不一样了用两个变量滚动更新时间复杂度 O(n)跑 n1000 都轻轻松松。而且顺着递推的思路你可以自然过渡到生成器用 yield 把每次计算的结果吐出来调用方用 for 循环逐个接收内存占用极小。这个“延迟计算”的思路是 Python 里非常高级的概念虽说不指望第一遍刷题就完全掌握但你至少要知道有这么个东西。def fib_generator(n): a, b 0, 1 for _ in range(n): a, b b, a b yield a for num in fib_generator(10): print(num)讲真斐波那契数列是那种“一辈子写代码就会遇到无数次”的原型题。后面学动态规划它是最简单的入门例子学算法分析它是递归转递推的经典案例学生成器它又成了最好的演示工具。这题值得你反复重写每次都会有新的理解。3.2 程序 7列表复制浅拷贝与深拷贝的第一次碰撞第七题将一个列表的数据复制到另一个列表中。看到这道题很多人第一反应是直接赋值list2 list1。但这样写是有问题的因为 list1 和 list2 指向的是同一个内存地址你改 list1list2 也会跟着变。这在工程上是一个大坑尤其是处理多维列表和对象列表的时候。正确做法有四种切片复制list2 list1[:]、list() 构造list2 list(list1)、copy 模块的list2 copy.copy(list1)、以及列表推导式list2 [x for x in list1]。这四种都能实现对一维列表的独立复制但对嵌套列表要格外小心list1[:]只是浅拷贝如果 list1 内部还嵌套着列表那嵌套的那层仍然是同一个引用。import copy list1 [1, 2, [3, 4]] list2 list1[:] # 浅拷贝嵌套列表仍是同一个对象 list3 copy.deepcopy(list1) # 深拷贝完全独立 list1[2].append(5) print(list2) # [1, 2, [3, 4, 5]] —— 被影响了 print(list3) # [1, 2, [3, 4]] —— 完全不受影响这个知识点单看文字描述很容易懂但你不亲手跑一遍永远体会不到那个“哇原来 list2 也被改了”的瞬间有多深刻。这道题刷完我强烈建议你顺手研究一下 Python 里对象赋值、浅拷贝、深拷贝的区别这是一道高频面试题也是以后写代码必须养成的意识。3.3 程序 8乘法口诀表格式化输出的实战第八题输出 9×9 乘法口诀表。这题在经典 100 题里人气极高几乎每个学 Python 的人都写过。它考察的核心不是乘法运算而是循环嵌套和格式化输出。关键点在于怎么控制换行和空格。第一层循环控制行第二层循环控制列内层循环每次 print 之后用end参数避免自动换行等一整行输出完再手动换行。for i in range(1, 10): for j in range(1, i 1): print(f{j}×{i}{i*j}, end\t) print()这里有个小坑容易被忽略内层循环的范围是range(1, i1)而不是range(1, 10)。因为口诀表是下三角第 i 行只需要输出前 i 列。如果你用 range(1, 10)输出的就是完整的 9×9 矩阵那就不叫口诀表了。格式化输出建议直接用 f-stringf{j}×{i}{i*j}又简洁又直观。end\t用制表符对齐会比空格更好看。有些版本会额外加一个 if j i 判断来换行其实在每行结束统一 print() 换行就足够了没必要再加判断。我还见过一个很有意思的变体用递归循环实现口诀表。虽然递归写嵌套循环有点奇怪但它能帮你理解递归的调用栈是怎么一层层展开再返回的。有兴趣的可以玩玩没有兴趣跳过也完全不影响。3.4 程序 9暂停一秒输出time 模块初识第九题暂停一秒输出。这道题的终极答案就是一行代码time.sleep(1)。import time print(开始输出) time.sleep(1) print(一秒之后输出)就是这么简单但背后涉及一个很多初学者没意识到的问题程序是顺序执行的print 打出的内容会立即出现在屏幕上而 time.sleep(1) 会让当前线程挂起一秒钟之后才继续往下执行。在 Jupyter Notebook 或者某些在线评测环境里print 的内容可能有缓冲你需要加上flushTrue参数才能保证内容立刻显示。这个细微差别在写爬虫限速、模拟延时、进度条动画的时候都会遇到。这道题真正的练习价值在于你可以把它当作一个小玩具跟前面的日期时间结合起来写一个“每隔一秒打印一次当前时间”的循环。这基本上就是最简单的轮询程序原型——你在监控系统、定时任务里看到的各种“每隔 N 秒做某事”的逻辑底层就是这么个循环加 sleep。3.5 程序 10日期时间输出从控制台到实战第十题输出当前日期和时间。这题是用 datetime 模块玩一点小花样。from datetime import datetime now datetime.now() print(now) # 2026-04-23 15:30:45.123456 print(now.strftime(%Y-%m-%d %H:%M:%S)) # 2026-04-23 15:30:45strftime 的格式化符看着多但常用的就几个%Y 四位年份、%m 两位月份、%d 两位日期、%H 24 小时制小时、%M 分钟、%S 秒。字符串拼日期的时候这个东西比手动拼接快得多而且自动补零格式规范。这道题的扩展方向非常多。你可以尝试计算两个日期之间的天数差可以用 date 类型相减得到一个 timedelta 对象里面存着 days 和 seconds。也可以尝试把字符串解析成日期对象用 strptime 反向操作。再进阶一点用 datetime 做倒计时、做时区转换、做每天定时任务触发判断这些在真实的自动化脚本里都用得上。如果继续往深挖还有一个值得研究的点datetime.now()返回的是本地时间但如果你写的是一个部署在服务器上的程序服务器可能使用 UTC 时区这时候你就要用datetime.now(timezone.utc)或者在获取之后手动转换时区。很多初学者在本地跑没问题一上服务器发现日志时间差了 8 个小时就是因为没注意时区问题。4. 刷题避坑与调试那些年我踩过的 Python 坑4.1 常见报错和排查速查表这十道题你刷下来大概率会遇到下面几个经典报错。我整理了一张速查表方便你遇到问题的时候直接对照排查。报错信息出现场景排查思路TypeError: unsupported operand type(s) for : int and str程序 2 忘记类型转换检查 input() 返回值是否用 int() 或 float() 转换IndentationError: unexpected indent任何多行代码检查缩进Python 用缩进区分代码块Tab 和空格不要混用IndexError: list index out of range程序 4 日期切片检查列表索引是否越界month-1 是否小于 0TypeError: int object is not callable变量名盖住了内置函数是不是把 sum、list、range 这些名字当变量用了ValueError: invalid literal for int() with base 10程序 2 用户输入了非数字input 内容转 int 失败运行时检查输入格式或者用 try...except 捕获在这些坑里我最想单独拎出来说的是“变量名覆盖内置函数”。比如你写了一个变量叫list [1,2,3]后面再想调用list(abc)把字符串转列表就会报错因为 list 已经不是内置类型了。解决办法很简单变量命名避开内置函数名起名时多看一眼。这算是一辈子只踩一次、但踩过一次就再也不会忘的坑。4.2 我总结的三条刷题心法第一别怕写丑代码。程序 1 的 24 种组合你用三重循环写用 permutations 写都能得到正确答案。先求对再求好顺序不能反。写十行能跑的代码永远比写三行跑不起来的代码有价值。第二每道题至少准备两种解法。一种是最直观的“笨办法”一种是用 Python 特性优化的“简洁版”。两种写法的对比过程就是你理解 Python 设计哲学的过程。比如程序 2 的分段计费笨办法是长长的 if...elif...换用查表法之后代码结构立刻清晰许多。这种“从长到短”的改写练习是提升代码品味最有效的方法。第三把每道题当成一个可扩展的模块来写。程序 9 暂停一秒你可以扩展成倒计时器程序 10 打印时间你可以扩展成定时提醒脚本程序 6 斐波那契你可以加上 memoization 缓存机制变成高性能版本。这样刷题十道题刷完你手里相当于攒了十几个可复用的代码片段而不是做过就忘的十道题。坦白讲这十道题的难度放在今天可能连入门级算法题的边都够不上。但它们在“帮你把基础语法转化为编程手感”这件事上的效率至今没有过时。我自己每次带新人还是会让他们先刷一遍这十道题不为别的就为了让新人在最快的时间内把手和脑都热起来。程序 1 到 10 的名气或许会被各种炫酷项目盖过但对真正想打牢基本功的人来说它永远是那套值得逐字逐句读懂的老题。