一些小例题

例题来自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) 的值.

  1. 一个直白的解法是先计算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. 寻找规律
    首先利用平方和公式: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 呢?

  1. 仍是最简单的思路,复杂度高的代码
    相当于对不同的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)
  1. 找规律:
    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

  1. 暴力解法。。。复杂度高
    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。

  1. 贪心解法(参考网上的代码)
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。

  1. 首先分析不能拼成正方形的情况,发现木棒小于等于四个可直接判断。对于木棒大于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)
  1. 回溯
    仍然是基于能拼成正方形的情况迭代判断。首先从最长的木棒开始若满足有可能组成边长则将此木棒移出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是以字符串的形式给定的。’‘’

  1. 使用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)
  1. 任意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

  1. 使用递归,超时
    得到(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))
  1. 动态规划:利用二维列表(仍然超时)
    先生成一个(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])
  1. 使用一维数组不断更新迭代,减小复杂度
    一维数组列表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,为奇数。

  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)
  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

  1. 暴力解法
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))
  1. 动态规划加粗样式
  • 判断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))
  1. 中心扩散法(仍然提示复杂度高)
    从中心开始向两边扩散,与暴力求解法不同的是,中心扩散法是从第一个字符开始,作为中心(指针开始处),设置向左和向右的指针,如果左指针和右指针指向的字符相同,那么就找到了一个回文串,左指针和右指针再分别向左向右扩散一步,再次比较两指针指向的字符是否相同…一直向外扩散,直到左指针和右指针指向的字符不同或者有一个指针到达字符串边界;然后将原字符串的第二个字符作为中心,进行扩散,直到最后一个字符。这样就找出一个字符串所包含的所有回文串了。
    字符串会是这种情况’acbbcb’,即有两个相同字符连续,此时’bb’也是一个回文子串,但是如果只使用上面的方法是没有办法找到的,这时需要用到双数中心,即使用两个相邻的字符作为中心进行扩散,比如以‘ac’为中心,以’cb’为中心,…,遍历一遍后就可以找出所有的重复字符串。前面说的以一个字符为中心的叫做单数中心。
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))
  1. 改进中心扩散法(复杂度为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))
  1. Manacher 算法(马拉车算法)
    一种专门用于解决“最长回文子串”问题的算法,时间复杂度为O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值