27.完全平方数

一、题目描述

给定正整数 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];
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值