-
Notifications
You must be signed in to change notification settings - Fork 1.4k
sum all primes
S1ngS1ng edited this page Jun 26, 2017
·
1 revision
- 这个
function
接收一个数字参数num
。返回小于等于num
的质数之和 - 如果
num
是4
,那么返回值应为5
。如果num
是10
,那么返回值应为17
- 这道题会涉及一些数学知识,其实代码不难写
- 质数的定义是,如果一个数 只能 被
1
和这个数自己整除,那么这个数就是质数。与这个概念相对应的叫合数 -
1
既不是质数也不是合数 - 比如,20 以内的质数,有且仅有这些:2, 3, 5, 7, 11, 13, 17, 19
- 那么首先我们需要写一个判断质数的方法。根据定义,可以这样写:
function isPrime(num) {
for (var i = 2; i < num; i++) {
if (num % i === 0) {
return false;
}
}
return true;
}
-
1
是不用判断的,因为任何整数都可以被1
整除。num
本身也是不用判断的,因为num
肯定可以被num
整除 - 我们先把这个写法用到基础解法中,后面再优化
- 上面我们已经写好了判断,那么只需要从
2
开始一直到num
遍历一遍,每一个数都进行一次判断,是质数的我们加起来就可以了
function sumPrimes(num) {
var sum = 0;
for (var i = 2; i <= num; i++) {
if (isPrime(i)) {
sum += i;
}
}
function isPrime(current) {
for (var i = 2; i < current; i++) {
if (current % i === 0) {
return false;
}
}
return true;
}
return sum;
}
- 首先,对于一个数字
x
,我们不需要从2
一直循环到x
来验证它是否为质数,只需要验证到x/2
就够了,也就是x
的一半。因为x
除以x/2
到x
的范围内的任何数,商一定是小于2
的。因此,对于isPrime
方法,我们就可以写成这样:
function isPrime(num) {
for (var i = 2; i < num / 2; i++) {
if (current % i === 0) {
return false;
}
}
return true;
}
- 因此,现在代码就是:
function isPrime(num) {
for (var i = 2; i < Math.sqrt(num); i++) {
if (current % i === 0) {
return false;
}
}
return true;
}
由于这里只是替换一个判断方法,就不再粘贴全部代码了。大家可以粘贴进去试一试。这样的优化对于较小的数看起来可能不明显,但当数字比较大的时候,速度确实会优化一些
- 判断质数的方式有很多。详情可以参考维基百科词条 素性测试 及其 英文版本
- 相比之下,最容易实现的应该是 Sieve of Eratosthenes(埃拉托斯特尼筛法) 了。详细内容请参考其 英文版本
- 简单说一下,这个算法就是在
2
至num
范围内,筛掉2
至Math.sqrt(num)
范围中质数的倍数,结果就可以得到2
至num
范围内的所有质数 - 执行过程是,先生成一个长度为
num
的数组,把所有的元素都设为true
。然后遍历这个数组,如果索引是2
的倍数 (不包含 2),就把元素标记为false
;再从头遍历,如果索引是3
的倍数 (不包含 3),就把元素标记为false
…… 以此类推 - 最终,索引范围在
2
至num
之间,元素为true
的就是质数
function sumPrimes(num) {
var flagArr = [];
// 生成标记数组,初始化为 true
for (var i = 0; i <= num; i++) {
flagArr.push(true);
}
for (var i = 2; i <= Math.sqrt(num); i++) {
var j = Math.pow(i, 2);
// 如果本身就是 `false`,说明已经标记过,因此不需要再进入循环
while (flagArr[i] && j <= num) {
flagArr[j] = false;
j += i;
}
}
// 计算最终标记为 true 的 index 之和
return flagArr.reduce(function (accum, value, index) {
return accum + (value ? index : 0);
}, -1);
}
- 如果你看不明白上面的解法,请去上面的维基百科页面看一下。词条里有一个 gif 图,应该会对你理解这个算法有很大帮助
- 强调一句,
flagArr
里的元素全都是Boolean
,作用是标记对应的索引是否为质数。因此最终求和,我们需要求索引的和 - 需要注意的是,题目中说了 "小于等于给定数值",因此生成
flagArr
以及筛选过程 (第二个for
循环) 都需要以=num
为边界 - 另一方面,注释中提到了。如果
flagArr[i]
本身就是false
,那我们就不需要进入while
循环了。因为while
循环里干的事儿是把目前还为true
的标记为false
,不存在把false
标记为true
的可能。至于为什么是j += i
而不是j += 1
,如果你理解了这个算法就会明白。可以尝试举一些实际的例子,带进去分析一下 - 最后的
reduce
,我们把初始值设置成了-1
。原因也很显然,因为flagArr
的index
是从0
开始的。索引0
对应的元素一定是true
,不会被修改。但0
不会影响最终结果。但1
对应的也一定是true
,循环中一样不会修改,因此在最终计算结果的时候要把最终的结果-1
。那么和初始值设置为-1
是一个道理 - 思路虽然是这样,写法却有很多。我们可以一上来就把
flagArr[1]
定义为false
,也可以在最后reduce
的回调函数里面判断一下index
是否等于1
。记得去处理这个情况就好,根源在于1
既不是质数也不是合数