质数求和 (Sum All Primes)
题目链接
问题解释
- 这个
function接收一个数字参数num。返回小于等于num的质数之和 - 如果
num是4,那么返回值应为5。如果num是10,那么返回值应为17
解题思路
- 这道题会涉及一些数学知识,其实代码不难写
- 质数的定义是,如果一个数 只能 被
1和这个数自己整除,那么这个数就是质数。与这个概念相对应的叫合数 1既不是质数也不是合数- 比如,20 以内的质数,有且仅有这些:2, 3, 5, 7, 11, 13, 17, 19
- 那么首先我们需要写一个判断质数的方法。根据定义,可以这样写:
1 | function isPrime(num) { |
1是不用判断的,因为任何整数都可以被1整除。num本身也是不用判断的,因为num肯定可以被num整除- 我们先把这个写法用到基础解法中,后面再优化
基本解法 - 遍历
思路提示
- 上面我们已经写好了判断,那么只需要从
2开始一直到num遍历一遍,每一个数都进行一次判断,是质数的我们加起来就可以了
代码
1 | function sumPrimes(num) { |
优化 - 数学方法
思路提示
- 首先,对于一个数字
x,我们不需要从2一直循环到x来验证它是否为质数,只需要验证到x/2就够了,也就是x的一半。因为x除以x/2到x的范围内的任何数,商一定是小于2的。因此,对于isPrime方法,我们就可以写成这样:
1 | function isPrime(num) { |
- 另外,其实判断到
x/2也是不必要的,判断到x^(1/2)就够了,也就是根号x(证明从略)。因此,现在代码就是:
1 | function isPrime(num) { |
由于这里只是替换一个判断方法,就不再粘贴全部代码了。大家可以粘贴进去试一试。这样的优化对于较小的数看起来可能不明显,但当数字比较大的时候,速度确实会优化一些
优化 - Sieve of Eratosthenes
思路提示
- 判断质数的方式有很多。详情可以参考维基百科词条 素性测试 及其 英文版本
- 相比之下,最容易实现的应该是 Sieve of Eratosthenes(埃拉托斯特尼筛法) 了。详细内容请参考其 英文版本
- 简单说一下,这个算法就是在
2至num范围内,筛掉2至Math.sqrt(num)范围中质数的倍数,结果就可以得到2至num范围内的所有质数 - 执行过程是,先生成一个长度为
num的数组,把所有的元素都设为true。然后遍历这个数组,如果索引是2的倍数 (不包含 2),就把元素标记为false;再从头遍历,如果索引是3的倍数 (不包含 3),就把元素标记为false…… 以此类推 - 最终,索引范围在
2至num之间,元素为true的就是质数
代码
1 | function sumPrimes(num) { |
解释
- 如果你看不明白上面的解法,请去上面的维基百科页面看一下。词条里有一个 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既不是质数也不是合数