例题来自PythonTip
因子平方和
6 的因子有 1, 2, 3 和 6, 它们的平方和是 1 + 4 + 9 + 36 = 50. 如果 f(N) 代表正整数 N
所有因子的平方和, 那么 f(6) = 50. 现在令 F 代表 f 的求和函数, 亦即 F(N) = f(1) + f(2) + … +
f(N), 显然 F 一开始的 6 个值是: 1, 6, 16, 37, 63 和 113. 那么对于任意给定的整数 N (1 <= N
<= 10^8), 输出 F(N) 的值.
- 一个直白的解法是先计算f(N)的值,然后依次F(N)=f(1)+f(2)+…+f(N)
问题时该算法复杂度高。
def f(N):
yinzi=[]
sum1=0
for i in range(1,N+1):
if N%i==0:
yinzi.append(i)
for item in yinzi:
sum1=sum1+item**2
return sum1
def F(N):
sum2=0
for i in range(1,N+1):
sum2=sum2+f(i)
return sum2
print(F(N))
- 寻找规律
首先利用平方和公式:1^2+2^2+3^2+...N^2=N *(N+1)*(2N+1)) // 6
然后观察F(N)
中i^2
出现的次数有什么规律:
对N=6
1^2……6次=N//1
2^2……3次=N//2
3^2……2次=N//3
4^2……1次=N//4
5^2……1次=N//5
6^2……1次=N//6
对N=9
1^2……9次=N//1
2^2……4次=N//2
3^2……3次=N//3
4^2……2次=N//4
5^2……1次=N//5
6^2……1次=N//6
7^2 ,8^2 ,9^2……1次=N//7,N//8,N//9
即F(N)
中i^2
出现次数为N//i
次,且对i>N//2
,i^2
仅出现1次
所以只需要计算1到N平方和再加上那些出现次数大于1的i^2
下面两个代码等价
def F(N):
he=(N*(N+1)*(2*N+1))//6#计算平方和
for i in range(1,N//2+1):
he=he+(N//i-1)*(i*i)#对每多出现一次的i^2,直接加到he中
return he
print(F(N))
def F(N):
he=(N*(N+1)*(2*N+1))//6#计算平方和
s=((N//i-1)*(i*i) for i in range(1,N//2+1))
he=he+sum(s)#先计算所有次数大于1的那些i^2的和,再与he相加
return he
print(F(N))
神的安排
如果我们定义 (n, m) 是一个安排(其中 1 < m < n), 而如果 C(m,2)/C(n,2) = 1/2, 它就是神的安排.
现在的问题是, 给你一个不大于 10^9 的正整数 N, 有多少组神的安排 (n, m) 满足 n <= N 呢?
- 仍是最简单的思路,复杂度高的代码
相当于对不同的n,m值遍历。比较C(n,2)和C(m,2)的的关系。另外还需要定义求组合数C(m,2)的函数
'''组合数求C(n,m)的函数
def C(n,m):#计算组合数C(x,y)=x!/(y!*(x-y)!)
a=b=result=1
minL=min(m,n-m)#使运算最简便
for j in range(0,minL):
#使用变量a,b 让所用的分母相乘后除以所有的分子
a=a*(n-j)
b=b*(minL-j)
result=a//b #在此使用“/”和“//”均可,因为a除以b为整数
return result
'''
def f(n):#计算组合数C(x,2)=x!/(2!*(x-2)!)
a=b=result=1
minL=min(2,n-2)#使运算最简便
for j in range(0,minL):
#使用变量a,b 让所用的分母相乘后除以所有的分子
a=a*(n-j)
b=b*(minL-j)
result=a//b #在此使用“/”和“//”均可,因为a除以b为整数
return result
N=21
count=0
l=[]
for n in range(2,N+1):
for m in range(2,n):
x=f(n)/f(m)
if x==2.0:
l.append((n,m))
count += 1
print(count,l)
- 找规律:
C(n,2)/C(m,2)=2
等价于n(n-1)/2=m(m-1)
,由此可以得到:m-1 = int(sqrt(n(n-1)/2))=int(x)
,去验证x(x+1)是否等于n(n-1)/2
但在网站提交也显示复杂度高,超时了。。。。
N=21
n = 2
count = 0
while n<=N:
x=n*(n-1)/2
y=int(x**0.5)
if x==y*(y+1):
count+=1
n+=1
print(count)
修改:
N=1000
n = 2
count = 0
l=[]
while n<=N:
x=n*(n-1)/2
y=int(x**0.5)
if x==y*(y+1):
count+=1
l.append(n)
n+=1
print(l)
结果:
[4, 21, 120, 697]
发现每两个符合的n值相差5倍以上,所以改成下面的形式.测试通过。
N=21
n = 2
count = 0
while n<=N:
x=n*(n-1)/2
y=int(x**0.5)
if x==y*(y+1):
count+=1
n=n*5
n+=1
print(count)
方法3:利用佩尔方程(这个也是看别人的代码才知道的。。。)
n(n-1)/2=m(m-1)
等价于n^2-n=2m^2-2m
,可以写成佩尔方程的形式:(2n-1)^2-2(2m-1)^2=-1
令x=2n-1,y=2m-1
,得到佩尔方程:x^2-2*y^2=-1
计算基本解为:x=3,y=2
从而方程的解的迭代格式为:(x,y) = (3x0+4y0,2x0+3y0)
x = 1
y = 1
count = 0 # 计数
while x <= 2*N-1:
count += 1
x = 3 * x + 4 * y#迭代解
y = 2 * x + 3 * y
print(count)
RSA密码方程
在RSA密码体系中,欧几里得算法是加密或解密运算的重要组成部分。它的基本运算过程就是解 (x*a) % n = 1 这种方程。 其中,x,a,n皆为正整数。现在给你a和n的值(1 < a,n < 140000000),请你求出最小的满足方程的正整数解x(保证有解). 如:a = 1001, n = 3837,则输出23
- 暴力解法。。。复杂度高
x*a%n=1
等价于((x%n) * (a%n)) % n = 1
,等价于找k,使得kn+1=xa
直接计算(1+k*n)%a
程序超时,可替换为a=a%n
def f(a,n):
a=a%n
for k in range(140000000):
if (1+k*n)%a==0:
return (1+k*n)//a
break
print(f(1001,3837))
排队
全班N(2<=N<=45)个人排成一排,但因为高矮不齐,需要进行调整。调整的方法是,不调换左右次序,只让若干人后退一步变为第2排,使第一排留下的人从左到右的身高按降序排列,即右边的人不比左边的人高。如果第2排的人还不按降序排列,则照此办理,即再让第2排的若干人后退一步变为第3排,这样继续下去,直到所有排的人都按身高从高到低排列。
现在将每个人的身高保存在列表L中,给你L,请你找出一种使第一排留下的人数尽可能多的调整方法,输出第一排留下的人数P及最后调整完共有几排数K,P和K之间以一个空格隔开。
如,L=[130, 122, 112, 126, 126, 125, 120, 100],则输出6 2。
def solve_it(L):
count = 0
l = []
flag=1#对第一行的情况标记
while len(L) >=1:
for i in range(len(L)):
if L[i] >= max(L[i::]):
l.append(L[i])#记录位于第一行的数字
if flag==1:#只有第一次循环,flag=1,这样x的值为第一行的数字个数
x=len(l)
if l==L:#第一行的数字和L相同,计数器+1,程序结束
count+=1
break
else:
count+=1#若第一行的数字和L不同,产生第一行,计数器+1
for i in range(len(l)):#记录除第一行的所有数字
if l[i] in L:
L.remove(l[i])
flag=0
l=[]
print(x,count)
solve_it(L)
平分果子
桌子上有一堆数量不超过20的果子,每个果子的重量都是不超过20的正整数,全部记录在列表 L 里面。小明和小红决定平分它们,但是由于他们都太自私,没有人愿意对方比自己分得的总重量更多。而果子又不能切开,所以最后他们商量好的平分方案是这样的:他们可以把某些果子扔掉,再将剩下的果子平分,请你求出在这种方案下他们每人最多可以分得的糖果重量。
例如,L = [1,2,3,4,5],则输出:7
L = [1,3,6],则输出:0
说明:对于样例1,他们最好的方案是把重量为 1 的果子扔掉,一人分得总重量为 7 的果子;样例2无法平分果子,因此答案是0。
- 贪心解法(参考网上的代码)
def f(L):
L.sort()
while L[-1]>sum(L)/2:#当序列最大元素大于和的一半,则去掉最大元素
L.pop(-1)
if len(L) >= 3:
continue
else:
return L[0] if len(L)==2 and L[0]==L[1] else 0
#若和数为奇数,则去掉最小的那个奇数,使整个列表的和为偶数
if sum(L)%2!=0:
for i in L:
if i%2!=0:
L.remove(i)
break
x=sum(L)//2#记录和的一半
temp=L[-1]#将L的最大元素赋给临时变量
l=L.copy()
#如果temp=x,则剩余元素和为x,平分完成。如果二者不相等,若列表中有L[i]=x-temp,则temp+L[i]=x,平分完成,返回x即可
#上面两种情况都没有,判断是否L[i]>x-temp,如果有这种情况说明目前列表L找不到平分方案,去掉最小值递归调用函数,若没有则执行temp += L[j]
for j in range(len(L)):
if temp==x or x-temp in L:
return x
elif x-temp<L[j]:
return f(l[1::])
else:
temp += L[j]
print(f(L))
拼接正方形
现在有一堆木棒,告诉你它们的长度,判断能否用这些木棒拼接成正方形。 注意:所有的木棒都要用上,且不能截断。 给你一个正整数list L, 如 L=[1,1,1,1], L中的每个数字代表一个木棒的长度,如果这些 木棒能够拼成一个正方形,输出Yes,否则输出No。 如L=[1,1,1,1],则输出Yes;L=[1,1,1],则输出No。
- 首先分析不能拼成正方形的情况,发现木棒小于等于四个可直接判断。对于木棒大于4个,若长度和不是4的倍数仍不能拼接。
长度和为4的倍数时,先拿出最长的木棒x,分情况讨论与x与边长temp之差。
x==temp则x自己就构成了一个边长,将x去掉,继续判断
temp-x在L中,设为L[i],则x和L[i]一起形成边长,去掉二者,继续判断
temp-x没在L中且差值<max(L),失败,无法拼接;temp-x没在L中且差值>max(L),寻找列表是否有和=temp-x(在下面的程序中这两种情况没有判断)
def f(L):
L=sorted(L,reverse=True)#降序排列
flag=1#标记是否可拼成正方形
if len(L)<=4:#木棍<=4个直接判断即可
if len(L)==4 and L[0]==L[1]==L[2]==L[3]:
flag = 1
else:
flag=0
else:
#木棍长度和不是4的倍数,不能拼成正方形
if sum(L)%4!=0:
flag=0
else:
#记录边长开始循环
temp = sum(L) // 4#记录边长
while len(L)>=1:
if max(L)>temp:#最长的木棍大于边长,一定不能拼成正方形
flag=0
break
for i in range(len(L)):
x =L[i]#优先把最大边挑出来作为边长(边长的一部分)
if temp==x:
L.remove(x)
break
elif temp-x in L[i+1:]:
L.remove(x)
L.remove(temp-x)
break
if flag==0:
print('No')
else:
print('Yes')
f(L)
- 回溯
仍然是基于能拼成正方形的情况迭代判断。首先从最长的木棒开始若满足有可能组成边长则将此木棒移出L
def f(L):
if max(L) > value:
return False
#size = len(L) # 记录当前列表最大值
#flags = [False] * size # 初始值为False,循环过程中如果使用了对应位置的木棒则标记为Ture
L.sort(reverse=True) # 降序排序,方便先取最大值出来
tempValue = [] # 记录本轮使用拼接正方形的木棒
i=0
while len(L)>0:
tempSum = sum(tempValue) + L[i]#记录当前长度
if tempSum==value:#成功
flag=1
L.pop(i)
tempValue = []
i=0
continue
elif value>tempSum:#边长大于当前值,可以添加
tempValue.append(L[i])
L.pop(i)
i=0
continue
# 如果遇到了遍历完一次后,存在tempValue不为0的情况,也就是本轮没有找到可以匹配的,就弹出最后一个数字使用的数字,同时将其添加回L中,继续往后找,用小的来拼;
if i == len(L) - 1 and len(tempValue) != 0:
if len(tempValue) == 1:#若tempValue中只有一个值且本轮没找到匹配的,则失败
flag=0
break
temp=tempValue[-1]
L.append(temp)
L.sort(reverse=True)
temp=L.index(temp)
# 弹出使用的最后一个数字
tempValue.pop()
while temp + 1 < len(L) and L[temp] == L[temp + 1]:#从比本轮使用的数字更小的开始,避免重复迭代
# print temp
temp = temp + 1
if temp + 1 == len(L):
flag=0
break
i=temp
i+=1
return flag
L=[2, 2, 2, 2, 3, 3, 3, 5, 5, 5, 6, 10]
if sum(L) % 4 != 0:
print('No')
else:
value = sum(L)//4#边长
print('No' if f(L)==0 else 'Yes')
回文数
‘’‘又是回文数!但这次有所不同了。 给定一个N进制正整数,把它的各位数字上数字倒过来排列组成一个新数,然后与原数相加,如果是回文数则停止,如果不是,则重复这个操作,直到和为回文数为止。
如果N超过10,使用英文字母来表示那些大于9的数码。例如对16进制数来说,用A表示10,用B表示11,用C表示12,用D表示13,用E表示14,用F表示15。
例如:10进制87则有: STEP1: 87+78=165 STEP2: 165+561=726 STEP3: 726+627=1353 STEP4: 1353+3531=4884
给你一个正整数N(2<=N<=16)和字符串M(“1”<=M<=“30000”(10进制)),表示M是N进制数,输出最少经过几步可以得到回文数。
如果在30步以内(含30步)不可能得到回文数,则输出0。输入的数保证不为回文数。 如N=10, M=“87”, 则输出4.注意:M是以字符串的形式给定的。’‘’
- 使用2个进制转换函数
f(M,N):将十进制数转换为N进制数;
f1(N,M):将N进制数转换为十进制数
注意当M为十进制数时,直接计算即可,无需使用上面2个函数
def f1(M,N):#10进制转N进制
l = '0123456789ABCDEF'
s = ''
while M>N:
r = M % N
M=M//N
s=l[r]+s
s=l[M]+s
return s
def f(N,M):#N进制数x变为10进制数,x为字符串
d = {'A':10,'B':11,'C':12,'D':13,'E':14,'F':15}
m=0
M=M[::-1]
for i in range(len(M)):
s = M[i]
if s in d:
s=d.get(s)
m+=int(s)*pow(N,i)
return m
count=0
M='87'
N=10
while M != M[::-1]:
if N == 10:
M = int(M) + int(M[::-1])
count += 1
M = str(M)
else:
M=f1((f(N, M)+f(N, M[::-1])),N)
count+=1
if count == 30:
count = 0
break
print(count)
- 任意N进制下的加法函数:对应位直接相加再再求模N下的余数即可,注意若相加之后的数大于等于N,要进位增加1
def f(N,M):#定义任意进制的加法函数
l='0123456789ABCDEF'
s=''
q=0
for i in range(len(M)):
m1=l.index(M[i])
m2=l.index(M[::-1][i])
m=m1+m2+q
if m>=N:
m=m-N
q=1
else:
q=0
s=l[m]+s
if q==1:
return '1'+s
else:
return s
count=0
while M!=M[::-1]:
M=f(N,M)
count+=1
if count == 30:
count = 0
break
print(count)
'''
def f(N,M):
d = {'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15}
d1 = {10:'A',11: 'B',12:'C',13:'D',14:'D',15:'F'}
m=[]
s=''
q=0
for i in range(len(M)):#两个N进制数相加
s1,s2=M[i],M[::-1][i]
if s1 in d:
s1=d.get(s1)
if s2 in d:
s2=d.get(s2)
temp=(int(s1) + int(s2)) + q
if temp>=N:
temp=temp-N
q=1
else:
q=0
m.append(temp)
if q==1:
m.append(1)
m=m[::-1]
for j in m:
if j in d1:
j=d1.get(j)
s+=j
else:
s+=str(j)
return s
count=0
while M!=M[::-1]:
if N==10:
M=int(M)+int(M[::-1])
count += 1
M=str(M)
else:
M=f(N,M)
count+=1
if count == 30:
count = 0
break
print(count)
'''
球迷购票问题
球赛门票的售票处规定每位购票者限购一张门票,且每张门票售价50元。购票者中有m位手持50元钱币,另有n人手持100元。假设售票处开始售票时无零钱。问这m+n人有几种排队方式可使售票处不致出现找不出钱的局面。 对给定的m,n(0<=m,n<=5000),计算出排队方式总数。
示例:
输入:m = 3 n = 2
输出:5
- 使用递归,超时
得到(m,n)状态的途径有2种,
1.(m,n-1)状态,后面再排队一个持50元的人
2…(m-1,n)状态,后面再排队一个持100元的人
所以状态转移方程为f(m,n)=f(m,n-1)+f(m-1,n)
。
注意还有2个特殊情况:
- n=0时,只有一种排队方案,即
mmmmmmm...
- n>m时,这时候无论怎么排队都会出现收100元但不能找回50顾客50元的情况,故此时排队方案数为0
def f(m,n):
if n==0:
return 1
elif n>m:
return 0
else:
return f(m,n-1)+f(m-1,n)
print(f(m,n))
- 动态规划:利用二维列表(仍然超时)
先生成一个(m+1)*(n+1)的列表来储存每一个状态,L[i][j]
表示f(m,n),所以有
状态转移方程:l[i][j] = l[i-1][j] + l[i][j-1]
#二维列表,仍然超时
if n==0:
print(1)
elif n>m:
print(0)
else:
l = [[0 for i in range(m+1)] for j in range(n+1)] # (m+1)*(n+1)维列表
l[0] = [1 for i in range(m+1)]
for i in range(1,n+1):
for j in range(i,m+1):
l[i][j] = l[i-1][j] + l[i][j-1]
#print(l)
print(l[n][m])
- 使用一维数组不断更新迭代,减小复杂度
一维数组列表L表示n为i时,对应的f(m,i)的值,m=0,1,2,…,m
初始化:L=[0,1,1,1]
,分别对应为[f(0,0),f(1,0),f(2,0),f(3,0)]
的值(因为f(0,0)=0是恒成立的)
迭代:
i代表n的值,j代表m的值。所以对于每一个i,只要j<i,就有L[j]=0
i=1时,要求[f(0,1),f(1,1),f(2,1),f(3,1)]
的值
由于0<1,所以:f(0,1)=0 ——> L[0]=0
我们知道f(m,n)=f(m,n-1)+f(m-1,n)
,所以f(1,1)=f(0,1)+f(1,0)
,即此时L[1] = i为1时L[0]的值+ i为0时L[1]的值
,
即L[j] = L[j] + L[j - 1]
,这样对于固定的i值,不断更新L中每个元素,遍历完成后的结果就是[f(0,1),f(1,1),f(2,1),f(3,1)]
的各个值。
L = [0] + [1] * m#初始化:L=[0,1,1,1]
if n==0:
print(1)
elif n>m:
print(0)
else:
for i in range(1, n + 1):
for j in range(0, i):
L[j] = 0
for j in range(i, len(L)):
L[j] = L[j] + L[j - 1]
print(L[-1])
C(n,k)
求组合数 C ( n , k) 的奇偶性. 给你n和k(1<=n<=10^9,0<=k<=n),若其为奇数,则输出1,否则输出0. 如n=2,k=0,则输出1. 因为C(2,0)=1,为奇数。
- 先计算组合数
C(n,k)
再判断奇偶性。(算法超时)
def C(n,k):#计算组合数(x,y)=x!/(y!*(x-y)!)
a=b=result=1
#n,k=max(x,y),min(x,y)
minL=min(k,n-k)#使运算最简便
for j in range(0,minL):
#使用变量a,b 让所用的分母相乘后除以所有的分子
a=a*(n-j)
b=b*(minL-j)
result=a//b #在此使用“/”和“//”均可,因为a除以b为整数
return result
if C(n,k)%2==0:
print(0)
else:
print(1)
- 找规律
n&k
:按位与,将n,k转换为2进制数,进行与运算。(都1则1,否则为0)
若:n&k=k
,则组合数则为奇数,否则为偶数。
证明
print(1 if n&k==k else 0)
最长回文子串
记得一副有趣的对联: “雾锁山头山锁雾, 天连水尾水连天”, 上联和下联都是回文的. 当然类似的还有: “上海自来水水来自海上, 山西悬空寺寺空悬西山”. 回文是什么意思? 就是把内容反过来读也是和原来一样的, 譬如 abccba, xyzyx, 这些都是回文的. 然而我们更感兴趣的是在一个英文字符串 L 中, 怎么找出最长的回文子串. 例如 L = “caayyhheehhbbbhhjhhyyaac”, 那么它最长的回文子串是 “hhbbbhh”. 这个任务看似简单, 但是如果我告诉你 L 的长度可能会接近 10^4, 问题似乎就变麻烦了. 不管怎么说, 加油吧骚年.
示例:
输入:L = “caayyhheehhbbbhhjhhyyaac”
输出:hhbbbhh
- 暴力解法
def f(L):
l=''#当前子串
max_s=''#最大回文子串
max_len=0#回文子串的最大长度
for i in range(len(L)):
for j in range(i+2,len(L)+1):
l = L[i:j]#当前子串
if l==l[::-1]:#当前子串为回文,且长度大于最大长度,更新当前最大回文子串及其长度
if len(l)>max_len:
max_len=len(l)
max_s=l
return max_s
#L = "caayyhheehhbbbhhjhhyyaac"
print(f(L))
- 动态规划加粗样式
- 判断
L[i:j]
是否为回文:
若L[i+1:j-I]
已经是回文了,那么只需要判断L[i]=L[j]
是否成立,若成立,则L[i:j]
是回文
所以状态转移条件:L[i+1:j-I]
是回文且L[i]=L[j]
- 建立一个二维列表
l
,用l[i][j]
来表示L[i:j]
是否为回文。若是,则l[i][j]=1
,否则为0。
这样,状态转移方程为:`l[i+1][i-1]=1且L[i]=L[j]
- 特殊情况:
当字符串长度为1时,肯定为回文;当字符串长度为2时,只需判断是否L[i]=L[i+1]
即可
def f(L):
l=[[0 for i in range(len(L))] for i in range(len(L))]#l[i][j]表示L[i:j]是否为回文,若是,则置为1
s,max_len=0,0#记录回文子串的起始位置以及长度
for end in range(len(L)):
for start in range(end+1):
if end-start<=1:#当子串长度小于2时,不存在l[start + 1][end - 1],单独考虑。只需要判断是否L[i]==L[i+1]即可
if L[start]==L[end]:
l[start][end]=1
else:
l[start][end]=0
else:
if L[start]==L[end] and l[start + 1][end - 1] == 1:#子串为回文且新加的两头字母相同,则这个新的串也是回文
l[start][end]=1
else:
l[start][end]=0
length=end-start+1#记录回文长度
if l[start][end]==1 and max_len<length:
s=start
max_len=length
#print(l)
return L[s:s+max_len]
#L = "caayyhheehhbbbhhjhhyyaac"
print(f(L))
def fun(L):
a = []
def f(l,r):#定义左右指针
while l>=0 and r<=len(L)-1 and L[l]==L[r]:
if len(L[l:r+1])>1:#只记录回文长度大于1的子串
a.append(L[l:r+1])
l-=1
r+=1
for i in range(len(L)):
f(i,i)
for i in range(len(L)-1):
f(i,i+1)
return max(a,key=len)
print(fun(L))
- 改进中心扩散法(复杂度为O(n^2))
中心扩散法的复杂度高是因为需要不断储存回文子串的列表,当字符串长度很大时,列表也会很大。所以试着想办法只储存当前回文长度最大子串,每次得到一个新的回文子串,就和当前回文子串长度进行比较,若新的回文子串长度更大,则更新列表
def f(l,r):#返回每个回文起始位置
while l>=0 and r<=len(L)-1 and L[l]==L[r]:
l-=1
r+=1
return l + 1, r - 1
def fun(L):
a = []#记录当前最长的回文子串
for i in range(len(L)):
l1, r1 = f(i, i)
l2, r2 = f(i, i + 1)
if r2 - l2 > r1 - l1:
r1, l1 = r2, l2
if len(a) < r1 - l1 + 1:#当前回文串长度是否为最大(是否更新)
a = L[l1: r1 + 1]
return a
print(fun(L))
- Manacher 算法(马拉车算法)
一种专门用于解决“最长回文子串”问题的算法,时间复杂度为O(n)