【Python习题】感悟&易错点总结

注:(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


未完

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值