题目要求
/**
* @author yangshuo
* @date 2020/3/18 13:36
*
* 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)
* 使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
*
* 示例 1:
*
* 输入: n = 12
* 输出: 3
* 解释: 12 = 4 + 4 + 4.
*
* 示例 2:
*
* 输入: n = 13
* 输出: 2
* 解释: 13 = 4 + 9
*/
拿到题目的感觉和之前做过的钱凑整数的题目差不多,我自己能想到的方法只有两种,一种递归,一种动态规划
题解里还有两种方法:
BFS和拉格朗日四平方和定理
先说结论,执行耗时递归>DP>BFS>拉格朗日四平方和定理
递归
递归的方式比较好实现,就是把每种情况都穷举一遍,看哪种组合用到的数字最小,递归过程中可以判断组合有没有出现过,剪枝之后,耗时会下降很多,不然通不过。
动态规划
public int numSquares(int n) {
// 完全平方数的数列
int[] num = new int[n + 1];
for (int i = 1; i <= n; i++){
num[i] = i; // 初始化当前i位置的数字,代表这个数组最大由多少个1组成
for (int j = 1; i - j * j >= 0; j++){
// 求num[i] 最少由几个数字组成
num[i] = Math.min(num[i],num[i - j * j]+1);
}
}
return num[n];
}
动态规划的推到公式:
假设最小公式值m=ƒ(n)
那么n的值满足下列公式 ∑(A[i] * A[i]) = n
令 k 为满足最小值 m 的时候,最大的平方数 。 令 d + k * k = n ; d >= 0;
注意:一定要是满足m最小的时候的k值,一味的取最大平方数,就是贪心算法了
得出 f(d) + f(k*k) = f(n);
显然 f(k*k) = 1; 则 f(d) + 1 = f(n); 因为 d = n - k*k;
则可以推出ƒ(n - k * k) + 1 = ƒ(n) ; 且 k * k <= n;
看的时候没明白为什么f(k*k)=1,想了一下,这里的1表示的是个数1,不是完全平方数1,因为d=n-k*k,所以一定存在一个完全平方数加上d状态下的累加值等于n,所以这里构成n状态的数字个数就是构成n-k*k状态的数字个数加1
BFS
宽度优先遍历的思想,是把想要凑成的数字N,作为根节点,然后按照每个完全平方数减,按顺序入队,每一次部分数字入队算作一层,那么N-i*i减到到小于0时,说明不能通过i*i来凑成N,如果等于0,那么说明凑成N的最小数量就是节点的层数。如果大于0 就入队,作为下次遍历的数字。
public int numSquares(int n) {
Queue<Integer> queue = new LinkedList<>();
int[] already = new int[n + 1];
queue.add(n);
already[n] = 1;
while(!queue.isEmpty()){
int poll = queue.poll();
for (int i = 1; ; i++){
// 小于0表示已经不能凑成目标数字了
int num = poll - i * i;
if (num < 0){
break;
}
if (already[num] != 0){
continue;
}
// 由于是广度优先遍历,所以第一次能刚好减成0的就是最小组成数字
if (num == 0){
return already[poll];
}
queue.add(num);
already[num] = already[poll] + 1;
}
}
return -1;
}
拉格朗日四平方和定理
定义
- 每个正整数均可表示为4个整数的平方和。
- 它是费马多边形数定理和华林问题的特例。
那么可以知道只有1,2,3,4 四种情况,并且有一个推论
那么就是先判断是不是自身就是完全平方数,返回1
再判断是不是(n - 7) % 8 == 0,是的话返回4
再判断从1开始两个数字的完全平方和可以等于n么,如果有的话,返回2
都不符合就返回3
public int numSquares(int n) {
if (Math.pow(Math.floor(Math.sqrt(n)), 2) == n) return 1;
while ((n & 3) == 0) {
n >>= 2;
}
if ((n & 7) == 7) {
return 4;
}
for (int y, x = 1; x * x < n; x++) {
y = (int) Math.floor(Math.sqrt(n - x * x));
if (x * x + y * y == n) return 2;
}
return 3;
}
微信公众号:二虎程序