ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Python第3章程序题全解析:水仙花数到金字塔打印的参考答案

Python第3章程序题全解析:水仙花数到金字塔打印的参考答案 被问得最多的永远是《Python语言程序设计基础》第3章程序部分的练习题答案。这一章恰好是很多人从“看得懂语法”到“写得出来程序”的分水岭。前两章背概念、抄代码还能应付到了第3章题目开始要求你自己拆问题、写逻辑、调错很多人第一次在这里卡住。这篇博文把第3章里最常布置的程序题答案整理成一套能直接运行、带逐行解释的参考实现覆盖水仙花数、九九乘法表、闰年判断、斐波那契数列、最大公约数、素数输出、猜数字、字符串统计和金字塔打印。所有代码只用Python标准库任意支持Python 3的编辑器都能直接跑。适合刚学完基础语法、正在赶作业复习的同学也适合已经写完但想对比写法的初学者。代码可以抄但更重要的是看我为什么这么写、哪些位置容易踩坑。1. 先说清楚第3章程序题到底在练什么1.1 这些题目为什么容易看懂却写不出来很多初学者有同样的困惑教材里每个知识点都认识input、print、int()、if、for、while单独拿出来都知道是干什么的可真拿到一道“求水仙花数”“输出九九乘法表”的题脑子就一片空白。这是因为语法知识和程序题之间缺了一层“拆解能力”。程序题本质上是在考“把解决过程翻译成代码步骤”的能力。我经常用做饭来类比语法相当于认识各种食材和调味料但一道菜最终好不好吃取决于你什么时候下锅、先炒什么、后放什么、火候怎么控。第3章的题就是这种“番茄炒蛋级”的练习看着简单但少一个步骤、顺序换一下结果就完全不对。拿水仙花数来说很多人第一反应是“我知道要判断但不知道怎么写”原因就是没有先想清楚怎么从三位数里取出百位、十位、个位。取位这个动作本质上就是整除和取余的组合。一旦你把“取百位”翻译成num // 100把“取个位”翻译成num % 10代码自然就写出来了。所以做这章题之前先别急着打开编辑器拿笔在纸上写三件事输入是什么、处理过程分几步、输出是什么。这三件事写清楚了代码只是翻译问题。1.2 做题前必须准备好的思维工具箱我自己做这章题时会刻意准备一套固定的“思维工具包”。你可以在做题前先过一遍后面每道题都往这几个方向靠。第一明确循环结构的选择。如果知道循环次数比如“输出1到100之间”“输出9行”优先用for如果不知道次数比如“一直猜直到猜对”“一直除直到余数为0”就用while。这个选择不是死规矩但能帮你少走弯路。第二警惕边界条件。第3章的题特别喜欢考边界闰年判断里1900和2000的区别、九九乘法表里内层循环到i而不是永远到9、斐波那契数列里n1和n2的情况。很多代码看起来没问题一测边界就废就是因为做题时没有主动想“最特殊的情况是什么”。第三每写一个循环先回答三个问题循环变量是谁每轮之后往哪走什么时候停下来这个习惯能从根源上避免死循环和循环次数错乱。我后面拆题时会反复用这套工具你跟着过一遍就会感觉到它比死记代码实用得多。2. 基础经典题参考答案与逐行拆解2.1 水仙花数取位运算第一次真正派上用场题目要求通常是这样输出100到999之间所有的水仙花数。所谓水仙花数就是一个三位数各位数字的立方之和等于它本身比如153因为 (1^3 5^3 3^3 153)。这道题的核心不在“立方”或者“判断”而在“怎么把三位数的每一位取出来”。百位最好办直接用整除num // 100比如153除以100取整得到1。个位也简单用取余num % 10153对10取余得到3。容易出错的是十位很多新手会写num / 10 % 10在Python 3里这得到的是浮点数跟整数比较时容易出问题。正确写法是num // 10 % 10先整除把数字变成两位数再取余得到十位数。参考代码for num in range(100, 1000): bai num // 100 shi num // 10 % 10 ge num % 10 if bai ** 3 shi ** 3 ge ** 3 num: print(num)运行后应该是四个数153、370、371、407。这里有一个特别常见的错误把写成。是赋值才是判断相等。如果你写成了if bai ** 3 shi ** 3 ge ** 3 num:解释器会直接报语法错误报错位置通常在赋值号附近看到类似报错先检查这里。 另外判断立方时个人更推荐**而不是连乘三个数因为可读性好也少写括号。2.2 九九乘法表嵌套循环不是靠死记九九乘法表的经典问法是输出左下三角结构第一行1个算式第二行2个算式直到第九行9个算式。这道题几乎必考同时也很容易写到一半就跑偏。核心思路是两层循环外层循环控制行从1到9内层循环控制每一行里打印几个算式。内层的循环范围不是固定的1到9而是从1到当前行号i。这一步想通了整道题就通了。for i in range(1, 10): for j in range(1, i 1): print(f{j}x{i}{i * j}, end\t) print()这里有两个细节值得注意。第一print(f{j}x{i}{i * j}, end\t)里的end\t表示每个算式之间用制表符分隔而不是默认的换行。很多新手把end忘了结果一个算式占一行整张表变成一列。第二内层循环结束后必须有一个不带end参数的print()来换行否则所有行都会挤在同一行里。这个换行的print()要放在内层循环外面、外层循环里面位置错了输出就会乱。用f-string格式化时{j}x{i}{i * j}输出的是“1x11”“1x22”这样的格式。如果你的运行环境是Python 3.6以下不支持f-string可以改成print(str(j) x str(i) str(i * j), end\t)或者print({}x{}{}.format(j, i, i * j), end\t)。 但我还是建议直接换到Python 3.x新版本这种写法简洁得多。我见过很多人在这一题上反复改错最后发现是外层range(1, 10)写成了range(10)导致多出一行0开头的算式或者i 1写成了i导致每一行都少最后一个算式。这种边界问题做的时候一定要心里默念循环范围取不到终点想包含终点必须1。2.3 判断闰年逻辑优先级是这道题的唯一难点判断闰年的规则大家都知道能被4整除但不能被100整除或者能被400整除。写成代码只有一行判断但这一行里藏着这章最容易忽视的逻辑优先级问题。year int(input(请输入年份)) if (year % 4 0 and year % 100 ! 0) or (year % 400 0): print(year, 是闰年) else: print(year, 不是闰年)我建议在and和or组合的时候把括号写清楚。虽然Python中and的优先级高于or即使不写括号year % 4 0 and year % 100 ! 0 or year % 400 0也会按你期望的顺序执行但写成括号之后读起来更直观也不容易在多种条件下搞混。为了省一个字符而牺牲可读性不值得。这一题的正确性必须用边界年份验证1900年能被4整除但不能被400整除所以不是闰年2000年能被400整除是闰年2024年能被4整除且不能被100整除是闰年。如果程序把1900判断成闰年说明条件里丢掉了一半如果把2000判断成不是闰年说明把“能被400整除”的条件写到了and里。用这三个年份一测题目基本就能下结论了。另外有一点要注意year % 100 ! 0是“不能被100整除”的意思不要写成not year % 100 0虽然逻辑等价但可读性很差。代码是给人看的尽量写直观的判断条件。3. 算法递进题循环、递推与函数的组合应用3.1 斐波那契数列前n项怎么输出最干净斐波那契数列的题目很常见但版本很多有的是求第n项有的是输出前n项。我先给前n项版本的参考答案因为它的写法更经典也能讲清递推思想。斐波那契数列的递推关系是第一项和第二项都是1从第三项开始每一项等于前两项之和。所以 (1, 1, 2, 3, 5, 8, 13, ...)n int(input(请输入要输出的项数)) if n 0: print(n 必须是正整数) else: a, b 1, 1 for _ in range(n): print(a, end ) a, b b, a b print()这段代码的核心是a, b b, a b。这一行的意思是先把a赋值为原来的b再把b赋值为原来的a b。两个赋值是同时完成的Python会先算右边的两个值再分别赋给左边的变量。这里有一个几乎所有初学者都会踩的坑如果你习惯性地拆成两行写a b b a b那b就变成了b b整个数列会变成 (1, 1, 2, 4, 8, 16...)完全错误。原因是第二行执行时a已经不是原来的a而是被上一句改成了b。所以我每次讲到这道题都会特别强调递推更新需要“同时拿到旧值”Python的元组赋值就是为了解决这种问题。除了实现细节你还可以观察一下为什么循环里能直接用print(a)因为a在每轮刚开始时正好是“当前这一项”打印完再推下一个循环次数正好等于项数。如果题目要求的是“求第n项”可以改成下面的写法n int(input(请输入 n)) if n 0: print(n 必须是正整数) elif n 1 or n 2: print(1) else: a, b 1, 1 for _ in range(n - 2): a, b b, a b print(b)这里循环n - 2次是因为第一项、第二项已经直接确定了从第三项开始递推循环正好推进到第n项。3.2 最大公约数辗转相除比暴力遍历好在哪最大公约数的题目最容易想到的解法是从较小的那个数开始往下降找到第一个能同时整除两个数的数字。比如求12和8的最大公约数从8开始试8不行7不行6不行5不行4可以答案是4。这种方法叫暴力遍历对小数字没问题但遇到两个比较大的数效率肉眼可见地差。教材第3章更想让你体会的是辗转相除法。它的原理用一句话说两个数的最大公约数等于较小数和“较大数除以较小数的余数”的最大公约数。用数学式子写就是 (gcd(a, b) gcd(b, a % b))不断套用直到余数为0最后一个非零余数就是答案。a int(input(请输入第一个数)) b int(input(请输入第二个数)) x, y abs(a), abs(b) while y ! 0: x, y y, x % y print(f最大公约数是{x})代码里的while y ! 0是这道题最容易绕晕的地方。每一轮循环里原来的y变成新的x原来的x % y变成新的y。可以拿18和24手动跑一遍第一轮x24, y1818不等于0执行完变成x18, y6第二轮x18, y66不等于0执行完变成x6, y0第三轮y0循环结束输出x6。答案正确。为什么用abs()因为教材题目一般只给正整数但万一遇到负数输入直接取余在Python里的结果会让你困惑。先转换成绝对值保证计算过程全部在非负整数上进行。如果两个数都是0数学上没有意义这个程序会输出0所以做输入校验时最好提醒自己限制为正整数也可以循环判断直到用户输入合法为止。3.3 素数输出范围优化与函数封装素数题有各种问法最常见的是“输出2到N之间所有的素数”。这道题能同时练到循环、判断和函数封装属于第3章里综合度较高的一道。先说判断素数的朴素做法一个数如果只能被1和它本身整除就是素数。判断时从2试到这个数本身减1有一个能整除就说明不是素数。但这样试到n-1太浪费了。优化点是不需要试到n-1只需要试到 (\sqrt{n}) 就够了。原因是如果 (n a \times b)那么 (a) 和 (b) 中一定有一个不大于 (\sqrt{n})如果存在因子在 (\sqrt{n}) 之前就能找到。import math def is_prime(num): if num 2: return False if num 2: return True if num % 2 0: return False limit int(math.sqrt(num)) 1 for i in range(3, limit, 2): if num % i 0: return False return True n int(input(请输入一个正整数)) for num in range(2, n 1): if is_prime(num): print(num, end ) print()这里做了几个层次的处理。先排除小于2的数和偶数再单独处理2因为2是唯一的偶数素数也正好是素数判断的起点。处理完这些特殊情况后循环从3开始步长为2只测试奇数循环次数进一步减半。如果你还没学到函数也没关系可以把is_prime内部的逻辑直接展开在主循环里但代码会显得很乱。我更建议把“判断一个数是否为素数”封装成一个函数因为输出素数这个任务本身就由两层逻辑组成外层遍历数字内层判断属性。用函数把内层逻辑独立出来主程序就一行if is_prime(num)读起来清爽得多。等学到后面章节你会越来越依赖这种“拆成小函数”的思维。4. 综合实战题随机数、字符处理与循环嵌套4.1 猜数字游戏循环条件与输入校验的配合猜数字是综合题里的熟面孔程序先生成一个1到100的随机整数用户去猜程序提示“大了”或“小了”猜对后显示猜的次数。这题既练while循环的“不确定次数”结构也练输入处理和比较判断。import random target random.randint(1, 100) count 0 print(我想好了一个 1 到 100 之间的整数开始猜吧。) while True: text input(请输入你的猜测) if not text.isdigit(): print(请输入正整数) continue guess int(text) count 1 if guess target: print(太小了再大一点) elif guess target: print(太大了再小一点) else: print(你猜对了一共猜了, count, 次) breakrandom.randint(1, 100)生成的是包含1和100在内的随机整数很多初学者会写成random.randrange(1, 100)那样100就永远不会出现了。这是非常典型的边界错误值得记一下。循环结构上因为不知道用户要猜多少次我用的是while True加break的写法。每猜一次count 1就必须执行一次。这句话的位置不要放在判断猜对之后再执行否则正确的那一次不会被计入。我在调试类似代码时经常看到这种情况最终答案往往是“次数少了一次”。代码里还加了一层输入校验text.isdigit()用来判断用户输入的字符串是不是全由数字组成。如果不是就提示用户重新输入并用continue跳回循环开头不增加猜的次数。如果没有这一步用户输入一个非数字字符int(text)会直接抛出异常。教材第3章可能还没教异常处理所以isdigit()是现阶段比较友好的防错办法。4.2 字符串统计字符类型的隐藏细节字符串统计题通常这样出输入一行字符统计其中英文字母、数字、空格和其他字符的个数。题目不难但有几个隐藏细节特别容易让“看起来对”的代码实际出错。比较稳妥的写法是text input(请输入一行字符串) letters digits spaces others 0 for ch in text: if (a ch z) or (A ch Z): letters 1 elif 0 ch 9: digits 1 elif ch : spaces 1 else: others 1 print(英文字母个数, letters) print(数字个数, digits) print(空格个数, spaces) print(其他字符个数, others)为什么我不直接推荐ch.isalpha()和ch.isdigit()因为Python的isalpha()是按Unicode字母判断的汉字也会被判为“字母”。如果题目要求统计“英文字母”用isalpha()统计出来的数字会把中文也算进去一看就错。同理isdigit()对全角数字、上标数字等Unicode数字字符也可能返回True比如².isdigit()结果是True。所以当题目明确要求“英文字母”和“数字字符”时直接用字符范围比较更符合题目的语义。空格判断我用的是ch 而不是ch.isspace()因为isspace()会把制表符\t、换行符\n等全部算进去如果题目只是让统计空格用空格字面量最准确。当然如果老师要求所有空白字符都算“空格”那用isspace()也说得过去。做题时先看清题目要求这个习惯比代码本身重要。4.3 金字塔打印嵌套循环的变式训练九九乘法表练了嵌套循环金字塔打印是同一类思想的进阶变式。常见题目是输入层数n输出一个由星号组成的等腰三角形。观察规律是这道题的关键。第1行有1个星号第2行有3个星号第3行有5个星号。第 (i) 行星号数量是 (2i - 1)。为了让三角形居中第 (i) 行前面还要放 (n - i) 个空格。n int(input(请输入层数)) for i in range(1, n 1): print( * (n - i) * * (2 * i - 1))这个代码非常短但如果不理解“空格数加星号数”的规律第二反应就是想着用两层循环去逐个打印字符。实际上Python的字符串乘法 * (n - i)可以直接生成指定数量的空格整个输出一下子变得很干净。等学到字符串这一章节你会觉得这个操作很自然但在第3章里能主动用上说明你已经有了“先观察规律再选工具”的意识。如果题目要求的是靠左的直角三角形那就更简单n int(input(请输入层数)) for i in range(1, n 1): print(* * i)这两道变式题的背后都是同一个思想把“每行输出什么”抽象成一个与行号 (i) 有关的表达式而不是机械地一层层写循环。5. 运行报错排查与作业避坑速查5.1 缩进、类型、死循环三类高频报错每次一到第3章练习群里问报错最多的问题基本可以归成三类缩进错误、类型错误、死循环。我挨个说建议你对照自查。第一类是缩进错误。Python用缩进表示代码块if、for、while下面一行如果没缩进解释器会直接报IndentationError。更隐蔽的是混用Tab和空格明明看起来对齐了一运行就报错。我的建议是编辑器统一设置成“把Tab自动转换成4个空格”然后整个文件只用缩进不用Tab。绝大多数现代编辑器默认都这么干但如果你在写作业时从网页复制代码最容易把Tab混进来。遇到缩进报错先把该代码块全部选中用编辑器转换成空格。第二类是类型错误。这种错误通常不是解释器直接报TypeError而是逻辑悄悄错。比如input()返回的是字符串直接拿去和整数比较Python 3里两个类型不同会抛TypeError: not supported。但更隐蔽的是第2.1节提到的情况用/而不是//取十位得到浮点数导致后面的立方和比较结果不对。遇到类型问题先搞清楚每个变量的类型必要时用type()打印出来看一下。第三类是死循环。常见场景有while循环里忘了更新循环变量比如做最大公约数时少了x, y y, x % y程序卡住不动猜数字循环里忘了break猜对了也不跳出还有循环条件是while True但没有出口。调试时如果程序一直不结束先按CtrlC强制中断然后检查循环体里是否有让条件最终变为False的语句。5.2 常见问题速查表第3章的程序题来来去去就是那几个坑我整理了一张速查表做完题对照检查一遍比自己瞎试快得多。问题现象常见原因解决方法IndentationError: unexpected indentTab与空格混用或不该缩进的地方缩进了全选代码统一转成空格检查代码块层级TypeError: not supported between instances of str and intinput()返回值没有转成整数用int(input(...))包裹水仙花数十位算错用了/而不是//十位写成num // 10 % 10九九乘法表每个算式都换行内层print没写end参数加end\t九九乘法表每行缺最后一个算式内层循环范围写成range(1, i)改成range(1, i 1)闰年判断1900年输出是闰年漏了“不能被100整除”的条件用完整规则year % 4 0 and year % 100 ! 0斐波那契数列输出变成1、1、2、4、8把a, b b, a b拆成两行赋值用元组同时赋值或先保存旧值素数判断把4也算成素数循环范围没加上1导致sqrt(4)开根后只试到2范围写int(math.sqrt(num)) 1猜数字猜对了但次数少1count 1放在break之后把计数语句放在判断之前字符串统计中文字符被算成字母用isalpha()判断英文字母改用a ch z or A ch Z这张表不可能覆盖所有报错但覆盖了第3章练习题里出现频率最高的几种。如果你遇到不在这张表里的问题我强烈建议你用最笨的办法把关键变量用print()打出来看每一步的值是否符合预期。程序就是一步一步跑出来的打印中间结果永远是最直接的调试方式。5.3 我的做题习惯和复查顺序最后分享一下我自己做这章题时的复查习惯。很多人写完代码运行一次看到输出没问题就交作业了但程序题的边界往往不是第一眼能看出来的。我的复查顺序很固定。第一步跑一遍标准答案。水仙花数要看到153、370、371、407四个结果九九乘法表要数一下第一行一个算式、第九行九个算式闰年判断要分别输入1900、2000、2024。第二步测边界值。斐波那契分别输入1、2、10素数输出分别输入2、3、4金字塔分别输入1、5。这些边界值最容易暴露问题。第三步如果时间充裕我会把同一道题换一种写法再实现一遍比如把for改成while或者把判断逻辑封装成函数。换一种写法不是为了炫技而是逼自己重新梳理一遍逻辑。很多时候你觉得代码“能跑了”其实只是碰巧输入没有踩到雷换一种写法就会发现自己对这道题的理解还不够透彻。这个习惯说起来简单但确实帮我避免了很多次“交作业时没问题上课展示时当场翻车”的尴尬。你可以挑一两道题试试尤其挑那些你做的时候有点犹豫的题。说回第3章本身。这些练习题看起来基础但它们真正想训练的是你对“输入、处理、输出”这一整套流程的把控能力。后面学到列表、字典、字符串方法时你会发现第3章练出来的循环思维和调试敏感度比任何一道具体的答案都值钱。所以就算你现在的代码还写得不漂亮只要能自己动手把每一题跑通、想明白为什么错就已经达到这一章的核心目标了。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表