注:(1) 为了方便查找,这里的章节数与教材对应,标号与 OJ 平台相匹配。
(2) Book 表示教材上的习题,详见:OpenJudge - OpenJudge
(3) Mooc 表示慕课上的课后习题,详见:OpenJudge - OpenJudge。仅收录与 Book 部分不重复的习题。
(4) 蓝色标题表示作为例题,在对应章节中有所讲解。红色标题表示有难度或值得一看。
(5) 每道题可能包含以下板块:易错,疑问,简化,优化,差异(与C++的不同),点睛。
Book
第2章
001 A+B问题
简化:
x = input()
numbers = x.split()
simplified:
numbers = input().split()
sum = int(numbers[0])+int(numbers[1])
print(sum)
simplified:
print(int(numbers[0])+int(numbers[1]))
002 字符三角形
简化:
print(a+a+a+a+a)
simplified:
print(a*5)
易错:print中不同写法导致分隔符的存在与否
下列写法会导致原本的空格与字符a之间还有一个空格:
print(" ",a)
这样写则没有分隔符:
print(" " + a)
003 计算(a+b)*c的值
WA 原因不明:
numbers = input().split()
sum = (int(numbers[0])+int(numbers[1])) * int(numbers[2])
print(sum)
正确示例:
numbers = input().split()
sum = (int(numbers[0])+int(numbers[1])) * int(numbers[2])
print("%d" % sum)
简化:利用好 Python 列表这一特性
s = input().split()
a,b,c = int(s[0[),int(s[1]),int(s[2])
print((a+b)*c)
004 反向输出一个三位数
易错:错用 split()
split() 是用来将一行用空格做间隔的字符串拆开读入,而 input 本身读入的是字符串,所以当用字符串思维操纵 input 读入的数据时不应该使用 split
易错:print 输出用 “,” 分隔而非 “+”导致多余空格的输出
007 求三个数的和
疑问:不太搞得清楚 Python 对小数的处理方式
程序如下:
n = input().split()
a,b,c = float(n[0]),float(n[1]),float(n[2])
sum1 = a+b+c;
sum2 = int(a)+int(b)+int(c)
print(a,b,c)
if(sum1 == sum2):
print("%.1f" % sum2)
else:
print(sum1)
输入:
1 2.3 4.56
输出:
1 2.3 4.56
1.0 2.3 4.56
7.859999999999999
诚然,我理解计算机中没有绝对相等的小数,所以可以默认为最后的输出结果等于 7.6。但是按照Python 数据类型定义 float 应该保留 6 位小数,所以本来我还在困惑怎么解决去除小数点后多余的 0 这一问题,目前看来多虑了。
附上去除小数点后多余的 0 的一种方法:
Python 中如何省略小数末位的零_python去掉小数点_devid008的博客-CSDN博客
008 字符串交换
易错:Python 与 C++ 不同,Python 中不允许通过下标直接对字符串作修改,故应该是按照要求构造新的字符串
009 字符串中的整数求和
差异:Python 中对 split 的字符串可以再用下标引用,即双下标(可能不算差异,某种程度上来讲有点像二维数组)
代码如下:
s = input().split()
num1 = int(s[0][0]+s[0][1])
num2 = int(s[1][0]+s[1][1])
print(num1+num2)
第3章
012 判断子串
简化:
s1,s2 = input(),input()
if s1 in s2:
print("yes")
else:
print("no")
#simplified
print(["no","yes"][input() in input()])
013 三角形判断
易错:这种错误是无关语言的,是通病!if 语句要注意 if 为假后的执行逻辑。
错误示例:显然若第二个或第三个 if 为假时不会有任何输出。
s = input().split()
a,b,c = int(s[0]),int(s[1]),int(s[2])
if a+b > c:
if a+c > b:
if b+c > a:
print("yes")
else:
print("no")
正确示例:
s = input().split()
a,b,c = int(s[0]),int(s[1]),int(s[2])
if a+b > c:
if a+c > b:
if b+c > a:
print("yes")
else:
print("no")
else:
print("no")
else:
print("no")
简化:以上代码显然是十分愚蠢的,写出来纯属因为忘了 Python 中的并且关系怎么写了
if (a+b>c) and (a+c>b) and (b+c>a) == 1:
014 简单计算器
简化:除非只保留商
print(int((a-a%b)/b))
simplified
print(a//b)
简化:用好 eval(),省去写各种计算方法的对应语句
s = input().split()
if s[2] not in ['+','-','*','/']:
print("Invalid operator")
elif s[2] == "/" and int(s[1]) == 0:
printf("Divided by zero!")
else:
print(int(eval(s[0] + s[2] + s[1])))
015 摄氏华氏温度转换
点睛:s[-1] 是字符串 s 的最后一个字符
点睛:字符串切片的功能(详见 6.2 节)
易错:对摄氏度、华氏度的判断:大小写均可(容易忘记小写)
易错:“题目要求:‘整数只保留到个位’ ” 和计算机本事对小数的表达有误差的缺陷冲突。分析如下:
这道题目有一个难点,就是计算结果是个整数时,不能以小数形式输出,即如果计算结果是4.0,就应该输出4。然而,转换公式里用到了小数1.8,因此算出来的结果一定是小数。如何判断一个小数是否应该被视为整数呢?计算机进行小数运算,是有误差的,如下面这段程序:
print(33.8-32) #>>1.7999999999999972 print(35.7-32) #>>3.700000000000003 print((33.8-32)/1.8) #>>0.9999999999999984 print((35.7-32)/3.7) #>>1.0000000000000007
前两行的输出不是我们期望的1.8和3.7,第3行和第4行也并没有如我们所希望的那样输出1.0,而都是输出了一个与1.0差值非常小的数(不同计算机,不同操作系统,不同Python版本导致的输出结果可能略有不同)。这都是由于计算误差导致的。计算有误差的根本原因是:在计算机内部,所有数都用二进制表示。一个位数有限的十进制小数,如0.7,表示成二进制形式,很可能就变成无限循环小数,而计算机显然无法存放无限循环小数,那么只能四舍五入,导致误差。
由第3、4行的结果我们可以看出,如果一个小数与跟它最接近的整数n的差的绝对值非常小,我们就可以认为,这个小数其实就等于整数n。至于这个“非常小”是多小,要看具体场景而定。如果是在OJ上做题,出题人一般不会在这一点上刻意难为大家,一般来说“非常小”取10−6即可。如果不行,可以把这个“非常小”改大些或小些再试。
解题程序:
eps = 1e-6 #10的-6次方 temp = input() if temp[-1] in ['F','f']: #如果输入华氏温度 c = (float(temp[0:-1]) - 32) / 1.8 if abs(c - round(c)) < eps: #abs(x)求x的绝对值 print("%dC" % round(c)) #以整数形式输出 else: print("%.2fC" % c) elif temp[-1] in "Cc": #如果输入摄氏温度。其实本行写else即可 f = 1.8 * eval(temp[0:-1]) + 32 if abs(f - round(f)) < eps: print("%dF" % round(f)) else: print("%.2fF" % f)
第1行:eps就是所谓的“非常小”的值,取106。
第3行:temp[-1]是字符串temp的最后一个字符。
第4行:用到了字符串切片的功能。若s是一个字符串,则s[x:y]是s的从下标x到下标y的左边那个字符构成的子串(切片)。例如:"12345"[1:3]就是"23","abcdef"[2:−1]就是"cde"。temp[0:1]就是temp去掉最右边那个字符('C','c','F'或'f')后剩下的部分。这部分要转换成小数再进行计算。计算的结果c一定是个小数。
第5行:在Python中,abs(x)能求x的绝对值,round(x)能求和x最接近的整数。如果小数c与跟它最接近的整数,即round(c)的差的绝对值小于eps,则认为c应该是个整数。
第10行:eval(temp[0:−1])把字符串temp[0:−1]看作一个Python表达式,求其值。不用eval用float也是一样。
上面的程序,用在OJ上做题没有问题,因为OJ题目的输入会严格符合描述。作为一个实际的温度转换工具,它就非常不称职了。首先没有提示用户该如何输入,其次,如果用户输入时忘了带结尾的"C"("c")或"F"("f"),比如输入21,则程序会在没有任何输出结果的情况下结束,弄得用户一头雾水。如果用户不小心按错键输入“a123F”,则程序会由于执行int('a123')这样不合法的转换导致RE,程序立即中止并输出难以理解的错误信息,吓用户一跳。总之这个程序对用户实在是太不友好。好的程序,不但能正确处理合法的输入,对用户输入非法等各种异常的情况也要进行处理,并优雅地进行提示,不应该发生RE的粗暴退出现象。
017 计算 2 的幂
疑问:Python 中 if 后面好像不能跟空语句?
025 求一元二次方程的根
各种语言程序设计的基础题目,思路很简单,注意几个不同语言的处理方式即可:
(1) 包含 math 库
(2) 关于输出只有负数输出符号(这里可以简单处理,但如果之后遇到正数要输出正号时,一定有简单的设定(如 C++ 输出流中的流操纵算子),而不是用 if 来判断)
第4章
027 从小到大输出正整数 n 的因子
易错:
range 是左闭右开的区间,若不说明,则 i 从 0 开始
031 寻找子串
优化:教材解析中提及的坑,可通过换种对比思路来避免。原文解析如下:
解题思路:从s1[0]开始,让s1中的字符和s2进行逐个比较,比较的时候要跳过s2中的空格。如果发现不相等的情况,就从s1[1]开始和s2从头比较……
解题程序:s1,s2 = input(),input() pos = -1 #要输出的答案 for i in range(len(s1)-len(s2)+1): #len(x)可以求字符串x长度 found = True #从s1下标i开始往后能否找到s2 ps1 = i #s1中要和s2进行比较的下一个字符的下标 for j in range(len(s2)): if s2[j] == ' ': continue #跳过s2中的空格 if s1[ps1] == s2[j]: ps1 += 1 else: found = False break #从s1[i]开始的比较已经失败,没必要继续比较了 if found: pos = i break #从s1[i]开始的比较已经成功,不必再试下一个i了 print(pos)
第3行:要比较的长度是s2的长度,所以如果s1中的比较起点的下标大于len(s1)−len(s2)就没意义了。
第8行:continue使得程序回到第6行,取下一个j。
第13行:这条break语句跳出第6行的那个循环,跳到第14行继续执行。
第16行:这条break语句跳出第3行的那个循环。本题s1如果比s3短,就算是一个小坑,不小心就会在这种情况下出错。
优化程序如下:先求出 s3 则不用特地考虑第 16 行提及的情况。
s1 = input()
s2 = input().split()
s3 = ""
for i in range(len(s2)):
if s2[i] != 0:
s3 += s2[i]
if s3 in s1:
for i in range(len(s1)):
if s3[0] == s1[i]:
print(i)
else:
print(-1)
032 求最小公倍数
优化:枚举固然是极其有效的解决方法,但通过数学可知,两数的最小公倍数为两数乘积除以最大公约数,该方法显然更加高效。原文解析如下:
解题思路:一种用计算机解决问题的基本思路,叫作枚举,也叫穷举。即逐个尝试所有可能的答案,加以验证,直到验证成功,就找到了答案。具体到本题,就可以从1开始,对每个整数判断是不是输入的3个整数的公倍数,如果是,就找到了答案。
s = input().split() x,y,z = int(s[0]),int(s[1]),int(s[2]) n = 1 while True: if n % x == 0 and n % y == 0 and n % z == 0: break #结束循环 n = n + 1 print(n)
第6行:如果程序走到本行,则直接结束循环,不会再次执行n=n+1。本程序也可以换种写法,即将第4行到第7行用下面两行替代:
while not (n % x == 0 and n % y == 0 and n % z == 0): #n不是x,y,z的公倍数就循环 n += 1
上面的程序效率不高,因为很多n根本没必要去验证其是否是答案。首先,没必要从1开始试,应该从3个数中最大的那个(假设叫m)开始试。另外,大于等于m的数,也不需要每个都试,只需要试m的倍数即可。
在穷举的时候,应该尽量减少无用的尝试,即避免花时间验证那些根本不可能是答案的选项。
本题程序可以改进如下:
s = input().split() x,y,z = int(s[0]),int(s[1]),int(s[2]) n = m = max(x,y,z) #从三者里面最大的开始试 while n % x or n % y or n % z: #n%x非0即为真 n += m #只试m的倍数 print(n)
第3行:在Python中,max(a1,a2,……,an)可以求n项里面最大的。这n项可以是数值、字符串、元组、列表等各种可以比大小的东西。
实际上,上面这个程序还可以进一步改进。比如,如果找到了m和x的最小公倍数k,那么以后就只需要试k的倍数即可。
优化程序如下:由于本题要求求三个数的最大公倍数,由数学性质可知,必须找全这三个数的所有公有质因数和独有质因数,将它们相乘得到最小公倍数。故有以下几种算法:1. 辗转相除法;2. 短除法。
详细代码可参考:python求三位数的最大公约数和最小公倍数(3种算法)_python求三个数的最大公约数_zzy_famatic的博客-CSDN博客
这里附上求两个数的最大公因数的代码:
a=int(input("please input the first number:"))
b=int(input("please input the second number:"))
# 首先要给两数排序,保证大数除以小数
m=max(a,b)
n=min(a,b)
t=m%n
while t!=0:
m,n=n,t # 仔细观察不难发现:每个除式的m、n是都是上一个式子的n和余数
t=m%n # 更新余数
print(f"{a}和{b}的最大公约数为{n}")
033 十进制数转二进制数
优化:充分利用 print() 函数中的输出格式控制符,可绕开短除法。原文解析如下:
解题思路:按照1.1节中提到的短除法,不停地除以2并取余数,将短除过程中得到的余数拼接成一个字符串,再倒过来,就是结果。
解题程序:
n = int(input()) if n < 0: n = -n print("-",end="") #输出负号 elif n == 0: print("0") exit() #结束程序执行 result = "" while n > 0: result += str(n % 2) #拼接余数 n //= 2 print(result[::-1])
第7行:exit()是Python函数,执行它会导致程序结束。
第12行:x[::-1]是将字符串x倒过来的字符串,后面7.2节会细说。
本题是有坑的。题目没说n不可以是负数。且n=0是一种特殊情况,处理逻辑和n>0时是不一样的。这种坑不止做题时才会出现,真实的软件开发中一样有。比如银行余额是0或者负数,可能就需要特殊处理。
实际上Python提供了函数bin(x)用来求整数x的二进制形式,例如bin(12)的值就是字符串"0b1100"。
优化程序如下:
n = int(input())
s = str(bin(n))
#以下程序是用于去除二进制输出前的 “0b”
if n < 0:
s1 = '-'
for i in range(3,len(s)):
s1 += s[i]
elif n == 0:
print("0")
exit()
else:
s1 = ""
for i in range(2,len(s)):
s1 += s[i]
print(s1)
034 求最大整数
解题思路:设置一个变量,比如maxV,用来记录到目前为止看到的最大整数。一开始maxV就取第一个整数的值。然后每看到一个整数x,就和maxV比较,如果发现x>maxV,就更新maxV为x。最后输出maxV。注意maxV的初始值不能设成0。样例里面虽然所有整数都是大于0的,但是题目并没有这么说。也可能所有整数都是负数—这也是个坑。
这个题目的难点在于,不知道一共有多少行输入。如果输入结束了(已经没有输入数据了),还执行input(),就会导致Runtime Error。因此可以使用异常处理语句,发现产生异常,就意味着输入结束,程序也就可以结束了。解题程序:
第6行:将maxV更新为maxV和int(x)中的更大者。这种写法比用if语句判断int(x)是否大于maxV然后再决定要不要更新maxV,更为简洁。
第8行:如果输入数据已经结束,执行input()就会引发异常,程序会跳到第10行执行,导致跳出循环,直接运行到第12行。于是输出maxV。
在本机运行,输入一行后,程序就会等着你输入下一行。如何告诉程序输入已经结束呢?在Idle或PyCharm中运行程序时,在新的一行按Ctrl+D组合键就可以表示输入结束。如果是用命令行方式运行程序,则在新的一行按Ctrl+Z组合键然后再按Enter键,可以表示输入结束。
进行异常处理时,try语句组里的语句应该尽可能少。不可能产生异常的语句,就尽量不要放在里面。
差异:通过 “Ctrl+Z组合键然后再按Enter键” 来结束输入的方式与 C++ 相同。上文给我们提供了 “通过判断异常” 的方式来结束输入,
s = input()
lst = s.split()
maxV = int(lst[0]) #maxV记录最大值,一开始假设是第一个整数
while True: #总是要执行循环
for x in lst:
maxV = max(maxV,int(x))
try:
s = input() #如果已经没有输入数据了还执行input,会产生异常
except Exception as e:
break
lst = s.split()
print(maxV)
这里提供另外一种判断输入结束的方法(即输入为空时结束程序,较为低端),程序如下:
for i in range(10):
if input() == "":
print("exit() called")
exit()
else:
print("program continueed")
continue
Mooc
020 A+B Problem
040 万年历
042 图像模糊处理
043 向量点积计算
052 寻找h3
未完