这是我参与更文挑战的第6天,活动详情查看: 更文挑战
平方数之和(题号633)
题目
给定一个非负整数 c
,你要判断是否存在两个整数 a
和 b
,使得 a2 + b2 = c
。
示例 1:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
示例 2:
输入:c = 3
输出:false
示例 3:
输入:c = 4
输出:true
示例 4:
输入:c = 2
输出:true
示例 5:
输入:c = 1
输出:true
提示:
0 <= c <= 231 - 1
链接
解释
这题啊,这题一看就是双指针。
因为没有什么更好的办法来确定a
和b
的值,只能一点点找,那找的话就是双指针了。
左边界很好确定——0,右边界也不难,只要取数字的平方根即可(取整)。
剩下的就是双指针的经典操作,这里不多赘述。
自己的答案(双指针)
var judgeSquareSum = function(c) {
var left = 0
right = ~~Math.sqrt(c)
while (left <= right) {
var res = Math.pow(left, 2) + Math.pow(right, 2)
if (res === c) return true
if (res < c) {
left++
} else {
right--
}
}
return false
};
对吧,一看就知道答案了,没啥可说的,看了看题解发现有些人在纠结双指针会不会错过正确答案,笔者倒是没怎么纠结,不知道为啥。
让笔者比较在意的是另外一种方法性能问题👇:
另外的方法(循环)
先看看代码👇:
var judgeSquareSum = function(c) {
for (let a = 0; a * a <= c; a++) {
const b = Math.sqrt(c - a * a);
if (b === parseInt(b)) {
return true;
}
}
return false;
};
讲真,乍一看这段代码笔者真没法有啥问题,甚至感觉比双指针性能更好一些。但笔者错了,不管是执行时间还是内存占用都比双指针夸张很多,这是为什么呢?
首先看看执行次数,笔者在while
和for
循环的内部都进行了次数统计,发现while
内部执行了17次,for
循环只有16次,甚至略胜一筹,可到底是什么让它们的差距那么大呢,其实仔细一对比就能发现了。
首先,大概去掉一些类似的计算,相差一次的循环可以抹掉了,因为其实两者的内部逻辑都比较简单。
接下来看a * a
和Math.pow()
的代码,二者都差不多,并且在循环过程中都执行了两次,这里也抹掉。
再往下看就是一些if...else
和对变量的赋值操作,由于差距不大,这里也抹掉了,那最后剩下的就是Math.sqrt()
,这也就是关键所在。
在双指针中,只在函数运行之初取了c
的平方根,在while
内部其实并没有,但在循环中,每一次循环都要调用了一次Math.sqrt()
,这就是根源所在了。
Math.sqrt()
多执行了15次。
更好的方法(数学)
好了,到这里就超纲了,完全不知道在说什么东西,答案说是首先要进行质因数分解,再判断所有形如 4k + 34*k*+3
的质因子的幂是否均为偶数。
贴一下代码:
var judgeSquareSum = function(c) {
for (let base = 2; base * base <= c; base++) {
// 如果不是因子,枚举下一个
if (c % base !== 0) {
continue;
}
// 计算 base 的幂
let exp = 0;
while (c % base == 0) {
c /= base;
exp++;
}
// 根据 Sum of two squares theorem 验证
if (base % 4 === 3 && exp % 2 !== 0) {
return false;
}
}
// 例如 11 这样的用例,由于上面的 for 循环里 base * base <= c ,base == 11 的时候不会进入循环体
// 因此在退出循环以后需要再做一次判断
return c % 4 !== 3;
};
想具体了解的可以看看官方解答,在这里,官方甚至还“细心”地贴了证明过程了地址。。。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇