本文正在参加「Java主题月 - Java开发实战」,详情查看:juejin.cn/post/696719…
这是我参与更文挑战的第6天,活动详情查看: 更文挑战
题目 - 完全平方数
279. 完全平方数
给定正整数 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
方法签名
public int numSquares(int n) {
}
First try
如果要找出最小的值,那么我们可以先从整个结果集入手。
先假设我们有这么一个函数:
函数F(N),F(N)返回组成N完全平方数的 最少数量。
那么
F(N+a*a) = 1 + F(N)
换一下位置和定义,就变成了:
F(N) = F(N-a*a) +1
同时我们对a进行定义
a在区间[0,squrt(N)]里
同时注意到N小于等于104,那么我们用104作为上限就可以了。
那么我们可以得出递归的方法:
public static int getMinNum(int n){
int max = (int)Math.sqrt(n);
if(n<0) return 104;
if(n == 0) return 0;
if(n == max*max) return 1;
int[] res = new int[max];
for (int i = 0; i < max; i++) {
res[i] = 1 + getMinNum(n-(i+1)*(i+1));
}
int min = 104;
for (int i = 0; i < res.length; i++) {
int re = res[i];
min = Math.min(min, re);
}
return min;
}
执行结果:
超出时间限制
最后执行的输入:
63
没关系,至少说明我们的递归公式,思路上正确了。
那么接下来我们就开始用DP数组来替代递归栈做记录。
dp数组记忆化
我们记:
dp[n]
为递归中F(n)的结果,并将递归修改为循环,同时:将对应的平方值初始化到DP数组中,除此之外不做其他的改动。
-
因为
dp[n] = 1 + dp[n - a*a]
那么我们需要从左边开始(数组的左边)开始循环计算DP。
- 如果从右边开始,可能会取不到值。
- 基于平方数的常识,我们容易得知在初始化之后,数组的左边的记录会比右边的密集。
得出结果如下:
public static int numSquares2(int n) {
int[] dp = new int[n+1];
int mx = (int)Math.sqrt(n);
for(int i = 0;i<=mx;i++){
dp[i] = i*i;
}
for(int i=2;i<=n;i++){
int min = i;
int nx = (int)Math.sqrt(i);
for(int j = nx;j>1;j--){
min = Math.min(min,1+dp[i-j*j]);
}
dp[i] = min;
}
return dp[n];
}
执行用时:34 ms, 在所有 Java 提交中击败了83.86%的用户
内存消耗:37.5 MB, 在所有 Java 提交中击败了65.62%的用户
我们可以将数组的初始化一并放到dp的计算之中,节省初始化的时间。
由于平方数并不是连续的值,因此dp数组不能使用滚动数组的方式进行优化替换,因此这道题纯DP的解法到这里就结束了。