题目链接:LeetCode279
Ps:拉格朗日四平方和定理
思路:根据分析,每个大的平方数会由许多小的平方数转移过来,于是可以写出这样一个转移方程: d p [ i + j 2 ] = m i n ( d p [ i ] + 1 , d p [ i + j 2 ] ) dp[i+j^2]=min(dp[i]+1,dp[i+j^2]) dp[i+j2]=min(dp[i]+1,dp[i+j2]),于是我们可以枚举n以内的所有状态转移到n以内的更大的平方数,最后dp[n]就是答案。
Code:
//224 ms
class Solution {
public:
int numSquares(int n) {
int dp[n+1];
fill(dp, dp+n+1, 0x3f3f3f3f);
dp[0]=0;
for (int i = 0; i <= n; i++) {
if (dp[i]^0x3f3f3f3f) //如果是完全平方数就能推到下一个
for (int j = 1; j*j+i <= n; j++)
dp[i+j*j]=min(dp[i+j*j], dp[i]+1);
else
dp[i]=0;
}
return dp[n];
}
};
我们发现,用上面那个转移方程把所有可能会把所有情况都给遍历一遍,即大于等于当前数字的前一个状态我们都去搜索它,显然,这一些状态我们可以check掉。于是,类似于bellman-ford算法优化成spfa算法那样,我们用一个队列来维护一个当前有更优的情况,利用这些更优的情况来更新才会找到比当前状态更优的状态,有个小优化是用一个vis表示一个点是否在队中,因为重复去考虑一个已经更新的点显然没必要(原因同spfa一样)。于是问题转成图论的问题。
Code:
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+1, INT_MAX);
vector<bool> vis(n+1, false);
queue<int> Q;
while (!Q.empty()) Q.pop();
Q.push(0);
dp[0] = 0;
vis[0] = true;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
vis[x] = false;
if (x == n) return dp[x];
for (int i = 1; x+i*i <= n; i++) {
if (dp[x+i*i] > dp[x] + 1) { //后面的状态没搜过或能够通过当前状态更新
dp[x+i*i] = dp[x] + 1;
if (!vis[x+i*i]) //不在队列中
Q.push(x+i*i);
}
}
}
return 0;
}
};
当然这题还有更优秀的拉格朗日四平方和定理:每个正整数均可表示为4个整数的平方和,证明戳这里。
另外,还有一个结论:满足四数平方和定理的数n(满足由四个数构成,小于四个不行),必定满足
n
=
4
a
(
8
b
+
7
)
n=4^a(8b + 7)
n=4a(8b+7)
关于这个自己暂时不会,先贴上大牛的代码:
class Solution {
public:
int numSquares(int n) {
// 去除4因子
while (n % 4 == 0)
n /= 4;
// 若除8余7,满足四平方定理,必由4个完全平方数组成
if (n % 8 == 7)
return 4;
// 再尝试将其拆成两个或一个完全平方数
for (int i=0; i*i <= n; i++) {
int b = sqrt(n - i*i);
if (i*i + b*b == n)
return !!i + !!b;
}
return 3;
}
};