简介:这份资料是面向Python初学者的经典例题练习合集,以PDF文档形式整理,收录了从基础语法到简单算法的常见编程题目,适合刚接触Python、需要靠动手练习来打牢基础的人群,也可作为课堂教学或自学阶段的配套习题使用。压缩包内共1个PDF文件,约203KB,篇幅16页,按题目顺序依次给出问题描述与参考实现代码,涵盖循环与条件判断、函数与math模块调用、日期时间处理、字符串格式化、列表与数组思路、素数检测及水仙花数等数学算法场景,还包括利润分段提成、兔子繁殖、日期天数差、分解质因数、成绩等级转换等典型题。每道题都配有可直接参考的代码片段,方便读者对照理解raw_input输入、for循环遍历、while循环判断与逻辑运算的写法,在模仿与改写中掌握数据类型、控制流与基本算法设计。目前已有713人学习,属于轻量级、便于随时查阅的练习材料。
1. 从一份 16 页的 Python 例题集说起:它到底值不值得刷
很多人第一次打开这类「Python 经典例题整理」的 PDF,看到的是raw_input、print后面不带括号、%5d这种格式化写法,第一反应是「这代码过期了,跑不起来」。但把它扔进 Python 3 解释器逐条改写之后会发现,这些题目真正想训练的东西和语法版本无关:枚举与去重、分段计费的边界处理、完全平方数的整数判定、日期差、素数筛、递归拆解、位运算。名企笔试和 LeetCode 简单档里,这些套路换个壳还在反复出现。
这份 16 页的资料按程序 1 到程序 58 编号,覆盖了循环嵌套、math/datetime/string模块、列表原地交换、筛法、递归、Tkinter 画布绘图。适合两类人:刚学完语法但写不出完整程序的入门者,用它把「会语法」变成「会解题」;以及准备笔试的从业者,拿它当低成本的算法热身集。麻烦在于它是 Python 2 时代的产物,直接复制粘贴必然报错,必须做一次系统性的迁移,这篇文章就围绕这个迁移和刷题流程展开。
2. Python 2 到 Python 3 的例题迁移与四道枚举类题目的重写
这一章解决「拿到资料第一步做什么」。不做版本迁移,后面所有题都只是纸面阅读。
2.1 迁移清单:raw_input、print、除法、编码声明
原文代码里有四类在 Python 3 下必然失败的写法,逐条对应改写规则。
| 原写法(Python 2) | Python 3 写法 | 出错表现 |
|---|---|---|
raw_input() | input() | NameError: name 'raw_input' is not defined |
print x | print(x) | SyntaxError: Missing parentheses in call to 'print' |
n /= i(整数除法) | n //= i或n = n // i | 结果变成float,循环判断n != 1行为异常 |
print '%5d'%(i) | print('%5d' % i) | 语法报错 |
# -*- coding:utf-8 -*- | 保留无妨,Python 3 默认 UTF-8 | 不报错,但属于冗余 |
/和//的差别在程序 14「分解质因数」里是致命的。原文用n/=i,Python 2 下是整除,Python 3 下 90/2 得到45.0,后续range(2, n+1)直接抛TypeError。这是初学迁移时最容易被忽略的一处。
2.2 程序 1:无重复数字的三位数,枚举法与去重的取舍
三种三位数排列,标准解法是三层循环加i != j != k,注意!=不能连写成数学形式。
# 生成 1、2、3、4 组成的所有互不相同且无重复数字的三位数 count = 0 results = [] for i in range(1, 5): # 百位,1~4 for j in range(1, 5): # 十位 for k in range(1, 5): # 个位 if i != j and j != k and i != k: results.append(i * 100 + j * 10 + k) count += 1 print(count, results)range(1, 5)上界不包含 5,正好覆盖 1 到 4。条件是三个两两不等式,写成i != j != k只在链式相等判断中有意义,这里必须用and连接。答案是 24 个。常见改进用法是itertools.permutations([1,2,3,4], 3),一行出结果,但面试时手写循环更能说明对枚举结构的理解,两者都该会。
2.3 程序 2:分段奖金提成的逆序遍历结构
这题的价值不在算钱,在于「从最高档位往下累加」的写法。原代码用两个列表arr和rat配合逆序索引,是个很好的模板。
# 分段累进提成:从最高档位开始逐段计算 profit = int(input('Enter the profit:')) thresholds = [1000000, 600000, 400000, 200000, 100000, 0] # 档位边界 rates = [0.01, 0.015, 0.03, 0.05, 0.075, 0.1] # 各段对应费率 bonus = 0 for idx in range(6): if profit > thresholds[idx]: bonus += (profit - thresholds[idx]) * rates[idx] print((profit - thresholds[idx]) * rates[idx]) profit = thresholds[idx] # 把利润压回当前档位,下一轮只算剩余部分 print(bonus)关键在profit = thresholds[idx]这一句:第一次循环处理的是超过 100 万的部分,算完后把profit降到 100 万,下一轮比较的就是「10 万到 100 万」这一段。两个列表必须一一对齐,thresholds[idx]是区间下界,rates[idx]是该区间费率。如果输入利润为负,这段代码不会报错但结果为零,实际项目里应该在入口加if profit < 0: raise ValueError。
2.4 程序 3 与程序 4:完全平方数判定和日期差
程序 3 的原始写法用math.sqrt(num+100)-int(math.sqrt(num+100)) == 0判断是否完全平方,浮点误差在数值不大时可用,但更稳的做法是取整后回代验证。
import math num = 1 while True: r1 = int(math.isqrt(num + 100)) # Python 3.8+ 提供整数平方根 r2 = int(math.isqrt(num + 268)) if r1 * r1 == num + 100 and r2 * r2 == num + 268: print(num) break num += 1math.isqrt返回整数平方根,避免sqrt的浮点误差;再用r*r == 目标值回代,判定就是精确的。程序 4 用datetime.strptime解析%Y%m%d格式,把当年 1 月 1 日也解析成datetime,两者相减取.days再加 1。注意strptime对非法日期(比如 20150230)会抛ValueError,生产代码里要用try/except包住,提示用户重新输入。
3. 循环、素数筛与因子分解:从暴力法到筛法的复杂度对比
第二章的四题偏枚举,这一章处理「关于整数的判定与分解」,是笔试高频区,也是性能意识开始出现的地方。
3.1 程序 12:试除法判素数与 sqrt 上界
原代码从 2 试到int(sqrt(i)),这个上界是对的:若 n 有因子,必有一个不超过 √n。
from math import isqrt def is_prime(n): if n < 2: return False for d in range(2, isqrt(n) + 1): if n % d == 0: return False return True primes = [i for i in range(101, 201) if is_prime(i)] print(len(primes), primes)isqrt(n) + 1是因为range右端开区间。101 到 200 之间共有 21 个素数。原始版本用flag变量控制循环退出,可读性一般,改成提前return更清晰。如果换成判 10^12 量级的单个数,试除法就力不从心了,那种场景要换 Miller-Rabin,但这题范围完全不需要。
3.2 程序 36:埃氏筛法,用空间换时间
100 以内求素数,筛法是标准答案,代码比逐个试除更短。
# 埃拉托斯特尼筛法:标记合数,剩下的即素数 n = 100 is_composite = [False] * (n + 1) for i in range(2, isqrt(n) + 1): # 只需筛到 sqrt(n) if not is_composite[i]: for j in range(i * i, n + 1, i): # 从 i*i 开始标记 is_composite[j] = True primes = [i for i in range(2, n + 1) if not is_composite[i]] print(primes)两个细节值得记:外层循环到sqrt(n)即可,因为更大数的倍数已经被更小因子筛过;内层从i*i起步而非2*i,省掉重复标记。复杂度 O(n log log n),比逐个试除的 O(n√n) 快一个量级。原文用a = [0]*101加标记-1,思路一致,只是布尔数组语义更直白。
3.3 程序 14:质因数分解中的整除与除尽
n = int(input('Enter a number:')) print(f'{n} =', end=' ') factor = 2 parts = [] while factor * factor <= n: while n % factor == 0: parts.append(factor) n //= factor factor += 1 if n > 1: # 剩余的大质因子 parts.append(n) print(' * '.join(map(str, parts)))内层while保证同一个因子被除尽,比如 90 会连续输出两个 2 之前的一个 2、两个 3、一个 5。外层条件用factor * factor <= n而不是factor <= n,因为 n 在不断缩小,循环次数大幅减少。循环结束后若n > 1,说明剩下的 n 本身是质数,必须补进去。原始代码用n /= i在 Python 3 下会得到浮点数,必须改成//。
3.4 程序 13 与程序 19:水仙花数与完数判定的边界
水仙花数的位数拆解用i % 10、(i // 10) % 10、i // 100,只适用三位数。若要写通用的「n 位自幂数」,改成字符串遍历或循环取位即可。完数判定要注意因子不包含自身,原代码用sum = n * -1预置负自身再累加因子,是个取巧写法,改成sum = 0然后只累加i < n的因子更清楚。另外完全平方数的因子成对出现,isqrt上界循环里n // i和i分别累加,能一次补齐一对因子,避免漏掉大于 √n 的那一半。
4. 递归、列表原地操作与 Tkinter 绘图的落地点
前三章都是纯计算题,这一章处理「会写循环但写不好递归」「会调库但不懂原地操作」「不理解 GUI 事件循环」这三类典型缺口。
4.1 程序 26 到 29:递归的基线与两种写法
递归题最容易踩的坑是忘记终止条件,或者终止条件写错。以程序 26 的阶乘为例:
def factorial(n): if n <= 1: # 基线条件:0! 和 1! 都是 1 return 1 return n * factorial(n - 1) print(factorial(5)) # 120程序 27 的字符串逆序输出用递归实现,思路上是「先打印最后一个字符,再递归处理前 n-1 个」。程序 28 的年龄问题,第五人比第一人大 4 个 2 岁,写成return fun(i-1) + 2,基线fun(1) == 10。程序 29 的位数统计和逆序打印把cnt作为参数在递归中传递,这里要留意:Python 没有引用传递,cnt是值传递,所以每层递归的cnt是独立的副本,打印语句放在基线分支里才能得到正确的总位数。
提示:递归深度默认限制约 1000 层,阶乘这类题目范围小无所谓,但如果处理上万层的数据结构遍历,要么改写成迭代,要么用
sys.setrecursionlimit调整并确认不会爆栈。
4.2 程序 37 到 40:列表原地交换与插入排序的手写
程序 40 的数组逆序用了a[i], a[l-i-1] = a[l-i-1], a[i],这是 Python 的元组打包解包,右侧先构造元组再赋值,不需要临时变量。手写排序(程序 37)时同样可以用这个技巧简化交换。
# 选择排序,原地交换,不依赖 sort() data = [5, 3, 8, 1, 9, 2, 7, 4, 6, 0] for i in range(len(data) - 1): min_idx = i for j in range(i + 1, len(data)): if data[j] < data[min_idx]: min_idx = j if min_idx != i: data[i], data[min_idx] = data[min_idx], data[i] print(data)程序 39 的「有序数组插入」是插入排序的单步版本:先append扩展一个位置,再从后往前把比新元素大的值右移,最后把新元素放到空出的位置。要点是查找插入点和移位要合并成一次从后往前的循环,否则每次插入都变成 O(n) 查找加 O(n) 移位。
4.3 程序 38 与位运算题目:矩阵下标的线性化
3×3 矩阵用一维列表存储时,主对角线元素下标是l[3*i+i],即l[0]、l[4]、l[8]。副对角线是l[3*i + (2-i)]。线性化的好处是输入输出简单,坏处是下标公式容易写错,建议先在纸上画出下标矩阵再动手。
位运算那几题(程序 51、54)在 Python 3 下要注意数字字面量:原文的a = 077是 Python 2 的八进制写法,Python 3 改成0o77。100 & 0x00F01这类取位操作和语言的版本无关,但打印时用bin()看二进制形式更直观。
4.4 程序 56 到 58:Tkinter 画布坐标与事件循环
原文from Tkinter import *在 Python 3 下要改成from tkinter import *,其余 Canvas 逻辑基本通用。
from tkinter import * root = Tk() canvas = Canvas(root, width=300, height=300, bg='green') canvas.pack() x0, y0, x1, y1 = 150, 150, 160, 160 for _ in range(19): canvas.create_rectangle(x0, y0, x1, y1) x0 -= 5 y0 -= 5 x1 += 5 y1 += 5 root.mainloop() # 进入事件循环,窗口才会持续显示create_rectangle的参数是左上角和右下角坐标,循环里每次把左上一角往左上移、右下一角往右下移,就画出同心方框。mainloop()不可省略,它是阻塞式的事件循环,少了它就只是建好对象然后程序退出。画圆用create_oval,起止坐标是外接矩形的左上和右下;画直线用create_line。缩放系数j += 0.3那种写法控制圆的增长速率,属于美术效果,不影响语法理解。
5. 把 58 道题跑成可回归的测试与改写检查点
零散地跑几道题容易自我感觉良好,真正检验掌握程度的是把这份例题集变成一套可重复执行的测试。这一章给一个具体的落地方式。
最直接的做法是每道题写一个solve_xxx()函数,输入通过参数传入,再用断言验证结果。以第二章的三个题为例:
import math def solve_permutation_three_digits(): """程序 1:返回三位数个数和列表""" res = [i * 100 + j * 10 + 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] return len(res), sorted(res) def solve_bonus(profit): """程序 2:分段提成""" thresholds = [1000000, 600000, 400000, 200000, 100000, 0] rates = [0.01, 0.015, 0.03, 0.05, 0.075, 0.1] bonus = 0 for idx in range(6): if profit > thresholds[idx]: bonus += (profit - thresholds[idx]) * rates[idx] profit = thresholds[idx] return bonus def solve_prime_count(lo, hi): """程序 12:区间素数""" def ok(n): if n < 2: return False return all(n % d for d in range(2, math.isqrt(n) + 1)) return [i for i in range(lo, hi + 1) if ok(i)] assert solve_permutation_three_digits()[0] == 24 assert solve_bonus(1100000) == 10000 + 4000 + 8000 + 12000 + 7500 + 10000 assert len(solve_prime_count(101, 200)) == 21断言里的数字是自己按分段公式手算出来的期望值,这样一旦改写引入错误就会立刻暴露。solve_permutation_three_digits用列表推导替代三层循环,逻辑等价但更紧凑;solve_bonus保持原地修改参数profit,注意这意味着不要在多线程环境共享输入变量。
几个改写时的检查点:
- 除法全部检查:搜索代码里所有的
/,确认是否需要改成//。整数题里用错一次,类型就从 int 变成 float。 - 输入用
input而非raw_input,并且对int()转换加try/except ValueError,因为用户输入字母时原代码直接崩。 - 打印格式统一到 f-string:老的
%5d、%d *在 Python 3.6+ 都能写得更清楚,比如print(f'{i} *', end='')。 - 随机数验证排错:对于枚举类题目,用小规模输入(比如把程序 1 的 range 改成 1~3)手动推演预期结果,再和代码输出对比,比盯着代码找 bug 快得多。
如果想更进一步,可以用unittest或pytest把这些断言组织成test_examples.py,每道题一个测试方法。跑一遍全绿,说明这 58 道题的核心逻辑你都真正吃透了,而不是抄了一遍能跑就完事。这套测试文件本身也能当作后续复习时的速查清单,哪道题忘了回来看对应的solve_xxx和断言,比翻 16 页 PDF 快。
本文还有配套的精品资源,点击获取