给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
【思路】
dp题其实就是递归题,我其实很讨厌说动态规划这个名词,因为给人一种很晦涩难懂的感觉。动规的题我喜欢就把它当成纯递归的题来做,然后再运用dp思想来用数组代替函数优化就行了。所以说白了,dp就是考察递归思想!
这题,我们用递归思维来想。因为它需要将一个正整数n拆分成几个完全平方数的和嘛,所以我一下就想到了,如f(12) = min{f(12 - 1), f(12 - 4), f(12 - 9)}
那只要我们每次把小于n的完全平方数找出来不就行了吗?
所以我用一个tag数组,预处理,所有小于n的完全平方数的tag值全被我标记成了1.利用这种索引思想,就可以很快的判断一个数是不是完全平方数了!
递归程序就可以很轻松的写出了:
class Solution {
public:
int tag[10000];
void findnum(int n)
{
for(int i = 1;i * i <= n;i++)
{
tag[i * i] = 1; //标记完全平方数
}
}
int f(int n)
{
if(n == 1) //边界
{
return 1;
}
else
{
if(tag[n] == 1) //自己就是完全平方数
return 1;
else
{
int min_res = 0x3f3f3f3f;
for(int i = 1;i * i <= n;i++) //找出所有比自己小的完全平方数,值为i * i
{
if(tag[i * i] == 1) //如果是完全平方数
{
int res = f(n - i * i); //根据递归式
if(res + 1 < min_res)
min_res = res + 1;
}
}
return min_res;
}
}
}
int numSquares(int n) {
findnum(n); //预处理,找出所有的完全平方数
return f(n);
}
};
好,现在我们已经写好了递归程序了,这个程序必然是超时的,我们只需要利用dp数组,把中间算过的结果存下来避免重复计算就好了,这就是动态规划加速的思想。那我们对着这个递归程序,其实动规程序也可以很轻松的写出来了
AC代码:
class Solution {
public:
int tag[10000];
int dp[10000];
void findnum(int n)
{
for(int i = 1;i * i <= n;i++)
{
tag[i * i] = 1; //标记完全平方数
}
}
int DP(int n)
{
dp[1] = 1; //对应递归中的边界
for(int i = 2;i <= n;i++)
{
if(tag[i] == 1)
{
dp[i] = 1;
}
else
{
int min_res = 0x3f3f3f3f;
for(int j = 1;j * j <= i;j++)
{
if(tag[j * j] == 1)
{
int res = dp[i - j * j];
if(res + 1 < min_res)
min_res = res + 1;
}
}
dp[i] = min_res;
}
}
return dp[n];
}
int numSquares(int n) {
findnum(n); //预处理,找出所有的完全平方数
return DP(n);
}
};
所以真的就是这样,动规的题,就把它当作纯递归的题来思考,dp只是一种优化手段而已。这样做,就不会再觉得动规的题很难很难了。如果说是因为想不到状态转移方程而不会做这道dp题,那只能说递归思维还不够,应该多练递归的题