最近一直纠结于一个点,在于二分法中的延申题,就是给出一个列表和一个数X,要我们在其中找出两个数a,b使a**n+b**n=X的数学问题,为了方便描述,以下中n取3.
其实很简单,首先我们要建立一个升序列表。
import random
random.seed(10) #保证每次随机选的数相同
A=[random.randint(1,10000) for _ in range(100000)] #尽量大一点,尽量取到每一个数
A.sort()
下面我们再定义一个二分搜索的函数
def binary_search(A,x): #搜索列表A找数x
i,j = 0,len(A)-1
while i<= j:
k=(i+j)//2
if A[k]==x:
return k
if x>A[k]:
i=k+1
else:
j=k-1
return -1 #没找到返回-1
基本思路是遍历A中的前n-1个数(留一个数用于匹配,此数记为a),并计算相应的立方根值((X-a**3)**(1/3)),看看这个值是否在A中,如果在就返回,不在就继续找,直到找完还没有就返回-1
这是正确的代码
import random
#二分查找
def binary_search(A, x):
########## begin ##########
# 请在此填写代码,找到x返回索引号,没找到返回-1
i,j = 0,len(A)-1
while i<=j:
k=(i+j)//2
if x==A[k]:
return k
elif x<A[k]:j=k-1
else:i=k+1
return -1 #没找到返回-1
########## end ##########
random.seed(10)
A=[random.randint(1,10000) for i in range(100000)]
A.sort()
X=eval(input())
founded=False
for i, a in enumerate(A[:-1]):#遍历前n-1个数
########## begin ##########
# 请在此填写代码,输出找到的a和b,没找到返回-1
Y=X-a**3
if Y<0:continue #注意Y有可能小于0
b=-1
for i in range(int(Y**0.5)):#判断Y的三次方根是否为整数 那么必然在一定的范围能能找到i**3=Y
if i**3==Y:
b=i
break
if b==-1:continue
idx = binary_search(A[i+1:], b) #logn
if idx != -1:
print(a, int(b))
founded = True
break
########## end ##########
if not founded:print('-1')
可以发现这个算法用了一个很聪明的办法判断Y的立方根是不是整数,那就是先开0.5次方,再遍历找开立方根得到的数i。因为如果Y是可开三次方根的整数,则i=Y**(1/3)一定是整数,稍微将幂指数放大一点就可以找到这个i了(因为range是左闭右开的),如果在这个范围内没有符合条件的那么就说明Y不是整数。
我想到了模除运算,整数模除1一定是0,便用了这个性质。
错误的代码
c=X-a**3
if c<0:continue
b=c**(1/3)
if b%1==0:
idx=binary_search(A[m+1:],b]
if idx!=-1:
print(a,b)
试了很多遍,都不能过评测
原因是但是这样与开方的运算结合是有问题的
开方运算返回的是浮点数,而浮点数在计算机中实际上是以二进制的形式保存的,有些数并不精确,它内层有自己的保存方法,只是返回给我们的是十进制。小数的二进制的换算方法是采用“除二取整,再排序”
具体实操是:将小数乘以2,取整数部分,余下的小数部分再继续乘以2,直到小数部分为0.
0.1*2=0.2 取整数部分0
0.2*2=0.4 取整数部分0
0.4*2=0.8 取整数部分0
0.8*2=1.6 取整数部分1
0.6*2=1.2 取整数部分1
0.2*2=0.4 取整数部分0
0.4*2=0.8 取整数部分0
0.8*2=1.6 取整数部分1
0.6*2=1.2 取整数部分1 可以发现这样会无限循环下去
但是内存有限呀,不可能一直算下去,因此python内层会默认算到第X位,就不管了,默认算完了,因此是有小的误差的,python会默认将其返回。0.1与0.2都是这样,因此1.1+2.2的结果并不是3.3。
也即是说再python的运算中,浮点数并不是最终的结果。能不用就尽量不用。
这也就解释了开三次根号得不到整数的原因。