一、题目描述
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

二、解题思路
首先把题目翻译一下:完全平方数就是物品(可以无限件使用),凑成正整数n就是背包,问凑满这个背包最少有多少件物品?这就是一道完全背包问题的题目。
第一步: 确定dp数组(dp table)以及下标的含义
dp[i]:表示最少需要多少个数的平方来表示整数 i。
第二步:确定递推公式
这些数必然落在区间 [1, 根号n ]。我们可以枚举这些数,假设当前枚举到 j,那么我们还需要取若干数的平方,构成 i-j*j,此时我们发现该子问题和原问题类似,只是规模变小了。这符合了动态规划的要求,于是我们可以写出状态转移方程。
dp[j] = Math.min(dp[j], dp[j-i*i]+1)
第三步: dp数组如何初始化
dp[0]表示 和为0的完全平⽅数的最⼩数量,那么dp[0]⼀定是0。0*0也应该算是一种,但是题目描述的是找到若⼲个完全平⽅数(⽐如 1, 4, 9, 16, …),题⽬描述中可没说要从0开始,dp[0]=0完全是为了递推公式。
第四步: 确定遍历顺序
这是一道完全背包问题,不涉及组合或者排列,所以外层for遍历背包,⾥层for遍历物品,还是外层for遍历物品,内层for遍历背包,都是可以的。这里以外层遍历背包,内层遍历物品为例:
for(int i=1; i<=n; i++){ //遍历背包
dp[i] = i;
for(int j=0; j*j<=i; j++){ //遍历物品
dp[i] = Math.min(dp[i], dp[i-j*j]+1);
}
}
其实上面遍历中一直搞不懂为什么要加上一个dp[i] = i,因为之前做类似的题貌似没有这个步骤呀,也就是为什么每次都要更新dp,下面分析一下,代码刚开始循环是初始情况如下:
dp = [0,0,0,0,0,0,0]
以n=6为例,先不加上dp[i]=i现在进入循环
外层循环第一轮:i=1
dp[i] = 0;
内层第一轮:
dp = [0,0,0,0,0,0,,0]
j=0时 dp[0] = 0; dp[i-j*j] = 0;
dp[n] = 0
内层第二轮:
dp = [0,0,0,0,0,0,0]
j=1时 dp[0] = 0; dp[i-j*j] = 0;
dp[n] = 0
其实只经过一次循环就可以看出,每次dp[i或者dp[i-j*j]]的取值都是从初始的dp = [0,0,0,0,0,0,]中去取的,所以下面不管再怎么循环,结果都是0。(如果不明白可以自己debug模式一步步调一下就知道了)
现在加上dp[i]=i进入循环再来看一下:
初始:dp = [0,0,0,0,0,0,0]
外层循环第一轮:i=1
dp[i] = 1;
内层第一轮:
dp = [0,1,0,0,0,0,0] 这里dp数组更新了
j=0时 dp[i] = 1; dp[i-j*j] = 1;
dp[n] = 0
内层第二轮:
dp = [0,1,0,0,0,0,0]
j=1时 dp[i] = 0; dp[i-j*j] = 0;
dp[n] = 0
外层循环第二轮:i=2
dp[i] = 1;
内层第一轮:
dp = [0,1,2,0,0,0,0] 这里dp数组更新了
j=0时 dp[i] = 2; dp[i-j*j] = 2;
dp[n] = 0
内层第二轮:
j=1时 dp[i] = 2; dp[i-j*j] = 1;
dp = [0,1,0,0,0,0,0]
dp[n] = 0
外层循环第三轮:i=2
dp[i] = 1;
内层第一轮:
dp = [0,1,2,3,0,0,0] 这里dp数组更新了
...........
这里就不在继续循环下去了,因为到这里就能看到不同了,
最后一轮:dp = [0,1,2,3,1,2,3], dp[n]=3
第五步:举例推导dp数组
以输入n=5为例:

三、代码演示
class Solution {
public int numSquares(int n) {
//确定dp
int[] dp = new int[n+1];
//初始化dp
dp[0] = 0;
//遍历
for(int i=1; i<=n; i++){ //遍历背包
dp[i] = i; //特别注意这里
for(int j=0; j*j<=i; j++){ //遍历物品
dp[i] = Math.min(dp[i], dp[i-j*j]+1);
}
}
return dp[n];
}
}
1343

被折叠的 条评论
为什么被折叠?



