leetcode-279. Perfect Squares

leetcode-279. Perfect Squares

问题描述如下:
Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, …) which sum to n.

For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

该问题要求求出能组成一个整数的最小的平方数是多少,即12=4+4+4,那么它的最小的平方数为3. 13=4+9,那么它的最小的平方数为2.
该问题有以下四种解法:(4种解法均来自https://discuss.leetcode.com/topic/24255/summary-of-4-different-solutions-bfs-dp-static-dp-and-mathematics

解法一:数学解法

主要运用的是拉格朗日4平方数定理:
即任何一个自然数都可以由不超过4个的平方数组成:
该定理的维基百科和证明如下:
https://en.wikipedia.org/wiki/Lagrange%27s_four-square_theorem
https://www.alpertron.com.ar/4SQUARES.HTM

于是,该完美平方数的证明可以转化为分别是否由1个,2个,3个,4个平方数组成的。

然后根据拉格朗日定理可知,只要一个数i满足
i=4 k(8m7)
k,m均为>=0的整数,说明i最小由4个平方数组成

如果一个数是平方数,则说明一个数由一个平方数组成

判断一个数是否由两个组成,先减去i的最大平方数,再判断剩下的数是否是平方数。

最终代码如下:

class Solution {
private:
    int is_square(int n)
    {
        int SqNum=(int)(sqrt(n));
        return (SqNum*SqNum==n);
    }


public:
    //根据拉格兰日的定理,每个整数都可以由4个平方数组成
    int numSquares(int n) {
    //是否能由一个数字组成
    if(is_square(n))
    {
        return 1;
    }

    //是否能由三个数字组成
    //根据拉格兰日定理可知
    //只要n=4^(k*(8*m-7))
    //先判断是否能被4整除
    while((n&3)==0)   //n%4==0
    {
        n>>=2;
    }
    if((n&7)==7)    //m%8==7
    {
        return 4;
    }


    //判断是否能由两个数字组成
    int sqrt_n=(int)(sqrt(n));
    for(int i=1;i<=sqrt_n;++i)
    {
        if(is_square(n-i*i))
        {
            return 2;
        }
    }
    return 3;

    }
};

解法二:动态规划

直接说明递推公式吧,递推公式如下

result[0]=0;
result[n]=min(result[n],result[n-j*j]+1);

每个一个数都可以由一个数加上一个平方数构成,由此关系可以构成一个递推公式

代码如下:

class Solution {
public:
    int numSquares(int n) {
    if(n<=0)
        return 0;
    vector<int> result(n+1,10000);
    result[0]=0;
    for(int i=1;i<n+1;++i)
    {
        for(int j=1;j*j<=i;++j)
        {
            result[i]=min(result[i],result[i-j*j]+1);
        }
    }

    return result[n];

    }
};

解法三:静态动态规划

即通过创建static vector,OJ的多次函数调用来对一个动态规划的表做到多次利用,即numSquares(100)其实只要调用numSquares(10)的表就好,不需要创建新的表,相当于利用了OJ的小trick。

代码如下:

class Solution {
public:
    int numSquares(int n) {
    if(n<=0)
        return 0;
    static vector<int> result({0});


    while(result.size()<=n)
    {
        int m=result.size();
        int minResult=INT_MAX;
        for(int i=1;i*i<=m;++i)
        {
            minResult=min(minResult,result[m-i*i]+1);
        }
        result.push_back(minResult);
    }

    return result[n];
    }
};

解法四:广度优先搜索

把小于等于n的平方看成一个图的结点,那么节点与节点之间是否相连取决于是否满足SquareNumber+i=j,i为原数字,j为结果数字,SquareNumber表示为平方数,同时维护一个数组cntSquareNumber,表示该结点是由几个平方数组成,这样利用广度优先搜索该图的平方结点直到SquareNumber+i=n,返回最终的最小的平方数,得到结果

class Solution {
public:
    int numSquares(int n) {
    vector<int> cntNumber;
    //说明数字由几个平方数组成的数组
    vector<int> cntSquareNumber(n);
    for(int i=1;i*i<=n;++i)
    {
        cntNumber.push_back(i*i);
        cntSquareNumber[i*i-1]=1;
    }
    if(cntNumber.back()==n)
    {
        return 1;
    }
    queue<int> resultQ;
    for(auto& i:cntNumber)
    {
        resultQ.push(i);
    }
    int result=1;

    while(!resultQ.empty())
    {
        result++;
        int size=resultQ.size();
        for(int i=0;i<size;++i)
        {

            int temp=resultQ.front();
            for(auto& j:cntNumber)
            {
                if(temp+j==n)
                {
                    return result;
                }
                else if((temp+j<n) && (cntSquareNumber[temp+j-1]==0))
                {
                    cntSquareNumber[temp+j-1]=result;
                    resultQ.push(temp+j);
                }
                else if(temp+j>n)
                {
                    break;
                }
            }
            resultQ.pop();
        }
    }
    return 0;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值