问题描述如下:
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(8m−7)
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;i1;++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;iint temp=resultQ.front();
for(auto& j:cntNumber)
{
if(temp+j==n)
{
return result;
}
else if((temp+j1]==0))
{
cntSquareNumber[temp+j-1]=result;
resultQ.push(temp+j);
}
else if(temp+j>n)
{
break;
}
}
resultQ.pop();
}
}
return 0;
}
};