完全平方数
给定正整数 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
提示:
1 <= n <= 104
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/perfect-squares
解题思路演化过程
第一次思路
首先对n求平方根结果为a,如果aa=n,则将a自减1,然后再n-(a-1)(a-1)。将得到的结果循环,但是不要再判断是否aa=n这种情况。每次执行都要sum加一。当n=1或者n=0时,跳出循环。如果最后n=1则sum+1,然后返回结果。
但是这样错误的,n=12的时候就会变成:9+3这样就会比4+4+4的多。
代码
func numSquares(n int) int {
sum := 0
one := 0
for n!=0&&n!=1 {
// 这样还是不对,当n=12的时候就会变成:9+3这样就会比4+4+4的多
sqrtVal := int(math.Sqrt(float64(n)))
if sqrtVal*sqrtVal==n&&one==0{
sqrtVal--
one++
}
sum++
n = n - sqrtVal*sqrtVal
}
if n==1 {
sum++
}
return sum
}
第二次思路
靠靠靠,这道题感觉是有毛病的,它说是之和,我认为就是是两个数以上才是之和,谁知道一个数也是之和,靠靠靠。如果是这样的话,完全就是一个01背包问题嘛。和之前作过的三数之和是类似的思路。
根据枚举发现,组合数中各个数一定是在[1,n的平方根]之间。我们可以从1开始枚举这些数,求出每次枚举对应[0,n]之间各个数最少需要多少个数的平方来表示整数 。并且遵循:n = n - i*i的规律进行循环计算。
因此可以假设dp[i][j]表示在1到i之间最少需要多少个数的平方来表示整数j。i的取值范围:[1,n的平方根],j的取值范围:[0,n]。对于ji*i的情况,dp[i][j]1。对于j<i*i的情况,我们只能取dp[i][j]=dp[i-1][j]。对于j>i*i的情况,我们可以不取也可以取,我们只要取其中的最小值。对于取:dp[i][j]=j/(i*i) + dp[i][j%i*i]。
代码
func numSquares(n int) int {
sqrtVal := int(math.Sqrt(float64(n)))
dp := make([][]int,sqrtVal+1)
for i:=0;i<len(dp);i++{
dp[i] = make([]int,n+1)
}
// 初始化边界
for i:=1;i<n+1;i++{
dp[1][i]=1
}
for i:=1;i<sqrtVal+1;i++{
if i>1 {
dp[i][1] = 1
}
}
// 第二次的时候,就不对啊
for i:=2;i<sqrtVal+1;i++{
for j:=2;j<n+1;j++{
// 如果小或等于,不取
if j<i*i {
dp[i][j] = dp[i-1][j]
}else if j>i*i {
// 如果大于
// 取,以及不取
tmp := j/(i*i) + dp[i][j%(i*i)]
dp[i][j] = min(tmp,dp[i-1][j])
}else if j==i*i {
dp[i][j] = 1
}
}
}
return dp[sqrtVal][n]
}
func min(x,y int)int{
if x<y {
return x
}
return y
}
超级强大的定律:四平方和定理证明了任意一个正整数都可以被表示为至多四个正整数的平方和。这给出了本题的答案的上界。
同时四平方和定理包含了一个更强的结论:当且仅n!=4k*(8m+7)当时,可以被表示为至多三个正整数的平方和。因此,当时n=4k*(8m+7),n只能被表示为四个正整数的平方和。此时我们可以直接返回 4。
对于不等的情况,我们可判断:
- 如果n是一个平方数的时候,直接等于1
- 如果n符合a^2 + b^2= n的时候,直接等于2
- 剩下的就是直接返回3。