Python算法——1.完美平方

写在前面

此为本人在学习Python过程中遇到的案例做的学习笔记,案例来自《Python算法指南——程序员经典算法分析与实现》,源代码参考该书及网上资料,若表达有误请指正,谢谢!

问题描述

给定一个正整数n,找到若干个完全平方数(例如:1,4,9,…),使得它们的和等于n,并且使完全平方数的个数最少。

问题示例

给出n = 12,返回3,因为12 = 4 + 4 + 4;
给出n = 13,返回2,因为13 = 4 + 9;
给出n = 15,返回4,因为15 = 1 +1 + 4 + 9.

问题解决

一开始,我的想法是用递归的方法,每次减去一个小于等于n的最大完全平方数,并将得到的差赋给n,用一个函数进行递归,每次调用记录数+1,直到得到的差为0。

例如13 - 9 = ,4,记录数为1;4 - 4 = 0,记录数为2;函数最后一级返回2.
但是问题在于12 = 4 + 4 +4,如果用这种思路得到的将是12 = 9 + 1 + 1 + 1,即返回4.
因此这个方法不可行。

于是尝试使用列举的方法进行寻找规律,在列了几十个数后发现,完全平方数由本身组成,返回1;部分数如10,13等由两个完全平方数组成,返回2;诸如7,15,23等数由四个完全平方数组成,返回3;其余数均由三个完全平方数组成。

这样一来问题就比较好解决了,由于sqrt()开方返回的是一个浮点数,若(int(sqrt(n)))** 2等于n本身,那么就说明n是一个完全平方数,返回1;取i在1至int(sqrt(n))中的整数,若i ** 2 + (int(sqrt(n - i)))** 2 = n,那么说明n由两个完全平方数组成,返回2;返回4的一系列数中可找出规律为n = 7 + 8k,即n对8取余得到7;余下的数便是返回3了。

书中给出的源代码也是如此操作的,源代码如下:

def numSquares(self,n):
        while n % 4 == 0:
            n //= 4
        if n % 8 == 7:
            return 4
        for i in range(n+1):
            temp = i * i
            if temp <= n:
                if int((n-temp) ** 0.5) ** 2 + temp == n :
                    return 1 + (0 if temp == 0 else 1)
            else:
                break
        return 3

原理仅仅是我根据列出来的几十个数得到的规律,并且我对其中while循环一项不理解,于是继续百度。

百度到了完美平方数的几种解法,有动态规划法,广度、深度搜索法,这里运用的是四平方定理法。

Lagrange 四平方和定理: 任何一个正整数都可以表示成不超过四个整数的平方之和。
满足四数平方和定理的数n(这里要满足由四个数构成,小于四个不行),必定满足 n = 4a ( 8 b + 7 )

这么一来,while循环的操作就能使计算过程更加简便了。

将函数放到类中(这一步可省略),定义主函数并输入值测试。

if __name__ == '__main__':
    print("输入初始值:")
    n = int(input())
    print ("初始值:",n)
    solution = Solution()
    print ("结果:",solution.numSquares(n))

测试结果如下:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

问题拓展

可不可以在判断完美平方数的同时将该数由哪些完全平方数组成呢?

百思不得其解,于是我又去求助度娘了。

最终在博主“一只老猿”的“完美平方数”文章找到了解决方法,不过他用的是Java版代码,我就没去细看,但是思路十分清晰。在其底下的评论中,博主“高山流水cyh”给出了Python的代码。

完整代码如下:

from  collections  import  deque  as  dq
import  numpy as np
import  math
import pandas as pd
def  perfect(n):
    a=np.repeat(-1,n+1)
    a[n]=n
    q=dq()
    q.append(n)
    sq=pd.Series(range(1,math.floor(math.sqrt(n))*2+1,2)).cumsum().values    
    while q.__len__()>0  and  a[0]<0:
        x=q.popleft()
        l=x-sq
        l=l[l>=0]
        l=l[a[l]==-1]
        q.extend(l)
        a[l]=x
    i=0
    j=a[i]
    while  i<n:
       print(j-i,end=" ")
       i=j
       j=a[i]

何为DFS算法?

DFS(Depth-First-Search)深度优先搜索算法,是搜索算法的一种。是一种在开发爬虫早期使用较多的方法。它的目的是要达到被搜索结构的叶结点 。

之前在学习数据结构时有接触过,依然可以理解为最短路径探求,求从第一个完全平方数到最后一个完全平方数的最短式子,代码具体实现思路及举例说明(取n = 14)如下:

1.创建一个包含n+1个数的行矩阵,前n个数赋值为-1表示未探索,第n+1个数赋值为n

	a=np.repeat(-1,n+1)
    a[n]=n

得到a = [-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 14]

2.创建一个duque对象,方便使用队列操作,将初始值n存为队列的第一个元素

	q=dq()
    q.append(n)

得到q = deque([13]),存取待访问路径入口

3.获取从1到n范围内的所有完全平方数存入行矩阵sq,作减数待用

    sq=pd.Series(range(1,math.floor(math.sqrt(n))*2+1,2)).cumsum().values
    #math.floor(x)取小于等于x的最大整数
    #series是一个一维数组,Pandas会默然用0到n-1来作为series的index,第一列为index,第二列为值
    #cumsum求累计次数
    #values取值
    #最终得到的是小于等于n的所有完全平方数

得到sq = [1 4 9]

4.开始寻求最短路径,每次从队头中弹出一个数,初始值为原数n,用弹出的数减去得到的矩阵sq得到余数矩阵l,此时已经走出路径的第一步,会有m种可能(m为sq行维度)
循环条件为队列中还有元素且存储访问标志的a的第一个元素小于零

    while q.__len__()>0  and  a[0]<0:
        x=q.popleft()
        l=x-sq

然后判断余数矩阵中的数是否大于等于零,余数为负数的路径是不合法的,在余数矩阵中会被清除;
再判断所得余数是否被探索过或者是否已存入待探索矩阵,不符合条件的被会清除,不必重复探索;
经过上述两轮更新,矩阵l中剩下的即合法且还未探索过的余数。

        l=l[l>=0]
        l=l[a[l]==-1]

再将l中的余数送入队列q中,进行下一轮探索;
将余数作为索引,此轮被减数x作为值,更新访问标志矩阵

        q.extend(l)
        a[l]=x

直到队列中元素都被探索完了或者有某个余数变为0,循环终止,代表最短路径搜索完毕

第一次循环:
x = 14
q = deque([])
l = [13 10 5]
l = [13 10 5]#第一次更新
l = [13 10 5]#第二次更新
q = deque([13 10 5])
a = [-1 -1 -1 -1 -1 14 -1 -1 -1 -1 14 -1 -1 14 14]
#索引为5,10,13的访问标志被更新为14,表示该处值由14作为被减数减去sq得到
循环条件成立,循环继续

第二次循环:
x = 13
q = deque([10, 5])
l = [12 9 4]
l = [12 9 4]#第一次更新
l = [12 9 4]#第二次更新
q = deque([10, 5, 12, 9, 4])
a = [-1 -1 -1 -1 13 14 -1 -1 -1 13 14 -1 13 14 14]
#索引为的4,9,12的访问标志被更新为13,表示该处值由13作为被减数减去sq得到
循环条件成立,循环继续

第三次循环:
x = 10
q = deque([5, 12, 9, 4])
l = [9 6 1]
l = [9 6 1]#第一次更新
l = [6 1]#第二次更新,索引为9处已有值,去掉元素9
q = deque([5, 12, 9, 4, 6, 1])
a = [-1 10 -1 -1 13 14 10 -1 -1 13 14 -1 13 14 14]
#索引为的1,6的访问标志被更新为10,表示该处值由10作为被减数减去sq得到
循环条件成立,循环继续

第四次循环:
x = 5
q = deque([12, 9, 4, 6, 1])
l = [ 4 1 -4]
l = [ 4 1]#第一次更新,去掉-4
l = []#第二次更新,1,4都已存入q中待探索
q = deque([12, 9, 4, 6, 1])
a = [-1 10 -1 -1 13 14 10 -1 -1 13 14 -1 13 14 14]
#状态不变
循环条件成立,循环继续

第五次循环:
x = 12
q = deque([9, 4, 6, 1])
l = [11 8 3]
l = [11 8 3]#第一次更新
l = [11 8 3]#第二次更新
q = deque([9, 4, 6, 1, 11, 8, 3])
a = [-1 10 -1 12 13 14 10 -1 12 13 14 12 13 14 14]
#索引为3,8,11的访问标志被更新为12,表示该处值由12作为被减数减去sq得到
循环条件成立,循环继续

第六次循环:
x = 9
q = deque([4, 6, 1, 11, 8, 3])
l = [8 5 0]
l = [8 5 0]#第一次更新
l = [0]#第二次更新,5已探索过,8已存入q中待探索
q = deque([4, 6, 1, 11, 8, 3, 0])
a = [ 9 10 -1 12 13 14 10 -1 12 13 14 12 13 14 14]
#索引为0的访问标志被更新为9,表示该处值由9作为被减数减去sq得到
循环条件不成立,循环结束

5.接下来便是按照探索路径反着回去取出该最短路径
余数为0时a矩阵的值为最后一段,该值作为最后一段的被减数,减去它本身得到零,为最后一段路径的完全平方数,输出该路径;
同时,该值又是上一路径的索引,可以取出上一路径的被减数,与其相减即可得到上一路径的一个完全平方数,输出该路径;
以此类推,取到第一段路径的数为n,路径全都被取出。

    i=0
    j=a[i]
    while  i<n:
       print(j-i,end=" ")
       i=j
       j=a[i]

i = 0
j = 9
第一次循环:
输出9 - 0 = 9 #差值为本路径的值
i = 9 #上一路径的索引,下一路径的被减数
j = 13 #上一路径的被减数
循环条件成立,循环继续

第二次循环:
输出13 - 9 = 4
i = 13
j = 14
循环条件成立,循环继续

第三次循环:
输出14 - 13 = 1
i = 14
j = 14
循环条件不成立,循环结束

最终输出的值为9 4 1,即13 = 9 + 4 + 1为最短路径

问题思考

输入n = 200时,程序给出的结果为196 4,而根据我们的运算可知,还有另外一最优解为100 100,能否罗列出不同的最优解呢?有时间再去探索一下。

写在最后

第一次写博客,仅为个人学习记录用,排版拙劣,思路及表达有误欢迎指正探讨!

猜你喜欢

转载自blog.csdn.net/weixin_47585015/article/details/108846046