一、题目
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
二、思路——动态规划
完全平方数有 1,4,9,16 … … 我们的总体思路是:对于一个给定的正整数 n,我们首先找到组成和的完全平方数的所有组合,然后从中找到个数最少的即可。
- 问题拆解:
问题拆解就是要找到原问题与子问题之间的联系。对于任意 k(1 <= k <= n),组成 k 的完全平方数个数最多的情况就是 dp[k] = k,比如 dp[5] = 5,5 = 1+1+1+1+1。然后每次减去一个完全平方数,看能否将个数减少,比如组成 5 的完全平方数的组合还可以是 dp[5 - 1 * 1] + 1 = dp[4] + 1,或者 dp[5 - 2 * 2] + 1 = dp[1] + 1。那么对于任意 k,组成和的完全平方数的个数最少的组合,即为
d p [ k ] = M a t h . m i n ( d p [ k ] , d p [ k − j × j ] + 1 ) dp[k] = Math.min(dp[k], dp[k - j \times j] +1) dp[k]=Math.min(dp[k],dp[k−j×j]+1) 其中 j = 1,2,3 … …, j × j j \times j j×j 表示平方数,当然还要保证 k − j × j ≥ 0 k - j \times j \geq 0 k−j×j≥0。
这样就找到了第 k k k 个问题和第 k − j × j k - j \times j k−j×j 个子问题的联系。 - 定义状态:
根据上面的分析,将状态定义为:组成 k ( 1 ≤ k ≤ n ) k(1 \leq k \leq n) k(1≤k≤n) 的完全平方数个数最少的组合。 - 推导状态转移方程:
问题拆解中我们已经得到了状态转移方程:
d p [ k ] = M a t h . m i n ( d p [ k ] , d p [ k − j × j ] + 1 ) dp[k] = Math.min(dp[k], dp[k - j \times j] +1) dp[k]=Math.min(dp[k],dp[k−j×j]+1) 其中 j = 1,2,3 … …, j × j j \times j j×j 表示平方数,且 k − j × j ≥ 0 k - j \times j \geq 0 k−j×j≥0。 - 寻找边界条件:
当 n = 0 时,组合数为 0。所以在创建 dp 数组时多开一位,存放初始状态 dp[0] = 0。
三、代码
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
for(int i = 1; i <= n; i++){
dp[i] = i;
for(int j = 1; i - j * j >= 0; j++){
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
}