前言
这是笔者在LintCode刷题的经历,本着共同学习的想法,讲述一下自己解题的心路历程,希望对各位读者有所帮助。
题目 完美平方
题目描述:
给一个正整数 n, 找到若干个完全平方数(比如1, 4, 9, ... )使得他们的和等于 n。你需要让平方数的个数最少。
举例:
//给出 n = 12, 返回 3 因为 12 = 4 + 4 + 4。
//给出 n = 13, 返回 2 因为 13 = 4 + 9。
思路:
首先,**贪心算法**明显是不适宜的,以12为例,如果使用贪心,则12 = 9 + 1 + 1 + 1,返回值为4,而不是样例中的3。
然后,也是我的第二个想法,**用贪心算法得到的数字为上限**,比如说12,上限为4,则如果12/1,12/2,12/3中有**能整除且整除结果为完全平方数**的,则为正确结果,
这个想法的代码如下:
public int numSquares(int n) {
int ret = numSqrt(n);
for (int i = 1; i < ret; i++) {
if (n / i * i == n) {
if (isSqare(n / i)) {
return i;
}
} else {
continue;
}
}
return ret;
}
private static boolean isSqare(int n) {
int temp = (int) Math.sqrt(n);
return temp * temp == n;
}
private static int numSqrt(int n) {
int temp = (int) Math.sqrt(n);
if (temp * temp == n) {
return 1;
} else {
return 1 + numSqrt(n - temp * temp);
}
}
当然,事实证明笔者想的太过简单了,当测试到1684时,系统给出的答案是2,而不是按照以上代码获得的4。那么问题出在哪呢?
还是找个实例,61 = 25 + 36, 结果应该为2,然后按照上述算法上限为5,且61是个素数,无法整除2,3,4,结果就会返回5,因此,这个算法也是有问题的。
正确解法:
我们设想一个场景,已知1-1000的完美平方需要的数的结果数组,如何推导1001的结果?
首先,1001 = 1 + 1000 或 2 + 999 ... 或 500 + 501,我们可以获取这500对组合中值最小的一对,我们不用担心这500对组合会错过正确答案(**因为无论答案的组合如何,除非本身就是完全平方数,否则肯定可以拆成两个数的和,比如说12 = 4 + 8,4代表1,8代表2,结果为3**)。
按照这个思路,我们进行一次初始化,从1开始的初始化,笔者初次选定的上限是9999,选定的算法过程如下:
假定现在要算的数字为n,
① 如果数字是完全平方数,返回1
②上述情况不满足,则返回 1 + ( n - 1) 一直到 n/2 + (n - n/2)最小值
这部分初始化的代码如下 :
int[] temp = new int[10000];
boolean flag = false;
public int numSquares(int n) {
// 只会初始化一次,可以重复调用
if (!flag) {
init();
flag = true;
}
return temp[n];
}
private void init() {
for (int i = 1; i <= 9999; i++) {
if (isSqare(i)) {
temp[i] = 1;
} else {
int min = temp[1] + temp[i - 1];
for (int j = 2; j <= i / 2; j++) {
if (temp[j] + temp[i - j] < min) {
min = temp[j] + temp[i - j];
}
}
temp[i] = min;
}
}
}
private static boolean isSqare(int n) {
int temp = (int) Math.sqrt(n);
return temp * temp == n;
}
(为了便于阅读,下标为1则代表1,下标为2则代表2)
其实以上解法就是动态规划解法的从底往上的实现,且将一定范围内的结果打表,经测试通过了检验,当然,上述算法将一维数组扩展成二维数组也可以记录具体分成了哪些数字,就留给读者自己思考了,可以用递归的方式将结果顺次输出。
结语
谢谢阅读,希望对读者解这道题的思路有所帮助~