问题

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少
给你一个整数 n ,返回和为 n 的完全平方数的最少数量

完全平方数是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是

示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4

示例 2:
输入:n = 13
输出:2
解释:13 = 4 + 9

思路

可能刚看这种题感觉没啥思路,又平方和的,又最小数的
我来把题目翻译一下:完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品?

动规五部曲分析如下:

  • 确定dp数组以及下标的含义

    • **dp[i]**:和为**i**的完全平方数的最少数量为**dp[i]**
  • 确定递推公式

    • dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]
    • 此时我们要选择最小的dp[j],所以递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);
  • dp数组如何初始化

    • dp[0]表示和为0的完全平方数的最小数量,那么dp[0]一定是0
    • 看题目描述,找到若干个完全平方数(比如 1, 4, 9, 16, …),题目描述中可没说要从0开始,dp[0]=0完全是为了递推公式
    • 非0下标的dp[j]应该是多少呢?从递归公式dp[j] = min(dp[j - i * i] + 1, dp[j]);中可以看出每次dp[j]都要选最小的,所以非0下标的dp[i]一定要初始为最大值,这样dp[j]在递推的时候才不会被初始值覆盖
  • 确定遍历顺序

    • 我们知道这是完全背包
      • 如果求组合数就是外层for循环遍历物品,内层for遍历背包
      • 如果求排列数就是外层for遍历背包,内层for循环遍历物品 ```java int[] dp = new int[n + 1]; int max = Integer.MAX_VALUE; for(int i = 0; i < dp.length; i++){ dp[i] = max; }

dp[0] = 0; for (int i = 0; i <= n; i++) { // 遍历背包 for (int j = 1; j j <= i; j++) { // 遍历物品 dp[i] = min(dp[i - j j] + 1, dp[i]); } }

  1. - 举例推导dp数组
  2. - 已输入n5例,dp状态图如下:
  3. ![640.webp](https://cdn.nlark.com/yuque/0/2021/webp/463136/1625367066017-22b98be1-22a1-4fa9-8188-759b8120b228.webp#clientId=ud818c6a8-a5d4-4&from=ui&id=u1e09195f&margin=%5Bobject%20Object%5D&name=640.webp&originHeight=656&originWidth=1016&originalType=binary&ratio=1&size=27468&status=done&style=none&taskId=uafbcf5d6-3058-4fff-b36b-a438d5039df)<br />dp[0] = 0<br />dp[1] = min(dp[0] + 1) = 1<br />dp[2] = min(dp[1] + 1) = 2<br />dp[3] = min(dp[2] + 1) = 3<br />dp[4] = min(dp[3] + 1, dp[0] + 1) = 1<br />dp[5] = min(dp[4] + 1, dp[1] + 1) = 2<br />最后的dp[n]为最终结果。
  4. ```java
  5. class Solution {
  6. public int numSquares(int n) {
  7. int[] dp = new int[n + 1];
  8. int max = Integer.MAX_VALUE;
  9. for(int i = 0; i < dp.length; i++){
  10. dp[i] = max;
  11. }
  12. dp[0] = 0;
  13. for (int i = 0; i <= n; i++) { // 遍历背包
  14. for (int j = 1; j * j <= i; j++) { // 遍历物品
  15. dp[i] = Math.min(dp[i - j * j] + 1, dp[i]);
  16. }
  17. }
  18. return dp[n];
  19. }
  20. }
class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        int max = Integer.MAX_VALUE;
        for(int i = 0; i < dp.length; i++){
            dp[i] = max;
        }

        dp[0] = 0;
        for (int i = 1; i * i <= n; i++) { // 遍历物品
            for (int j = 1; j <= n; j++) { // 遍历背包
                if (j - i * i  >= 0 && dp[j - i * i] != max) {
                    dp[j] = Math.min(dp[j - i * i ] + 1, dp[j]);
                }
            }
        }
        return dp[n];
    }
}

或

class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        int max = Integer.MAX_VALUE;
        for(int i = 0; i < dp.length; i++){
            dp[i] = max;
        }

        dp[0] = 0;
        for (int i = 1; i * i <= n; i++) { // 遍历物品
            for (int j = i * i; j <= n; j++) { // 遍历背包
                    dp[j] = Math.min(dp[j - i * i ] + 1, dp[j]);
            }
        }
        return dp[n];
    }
}