题目来源 leetcode 279
给定一个整数n,n可以分解为m个平方数的和,求m的最小值。平方数可以重复,例如12可以分解为4+4+4。
这个问题用动态规划比较好求解。如果对背包问题比较熟悉,最容易想到的方法就是将平方数看成数类物体,将这些物体组合(可重复),构成n的最小物体数就是答案。用dp[n]表示构成n的平方数的最小数目。则dp[n]=min(dp[n-k*i*i]+dp[k*i*i]),其中k*i*i小于等于n。但是,对于比较大的n,这个算法的效率就很低了。
现在深入思考一点。在得知dp[0]到dp[n-1]的情况下,有必要计算dp[n-k*i*i](k>=2)这样的情况吗?并不需要,因为将n拆成k个平方数a1和另一个数a2,再讨论最小平方数数目,相当于将n拆为一共平方数a1,和另一个数a3=a2+(k-1)*a1,并且在分解a3时运用不一定是最佳的分解方式,在这种情况下,结果一定不会比分解a3时用最佳分解方式更好,也就是说当k>=2时,结果一定不会比k=1时更好。举个例子,对于dp[10],dp[10-1*1]+dp[1*1]是一定比dp[10-2*1*1]+dp[2*1*1]更小的,因为dp[9]<=dp[8]+dp[1]<dp[8]+dp[2]。由此可以得到状态转移方程dp[n]=min(dp[n-i*i]+1)。状态数为n,处理每个状态时间为n^0.5,总时间复杂度为n^1.5。
int numSquares(int n) {
const int INF = 10000000;
vector<int> sq;
for (int i = 1; i*i <= n; i++)
{
sq.push_back(i*i);
}
vector<int> dp(n + 1, INF);
dp[0] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j<sq.size()&&sq[j]<=i; j++)
{
if (dp[i] > dp[i - sq[j]] + 1)
dp[i] = dp[i - sq[j]] + 1;
}
}
return dp[n];
}