首页 > 学院 > 开发设计 > 正文

Leetcode 204. Count Primes

2019-11-10 18:35:44
字体:
来源:转载
供稿:网友

Description:

Count the number of PRime numbers less than a non-negative number, n.

s思路: 1. 先看看这道题的边界是什么。肯定需要检查每个数i是否质数,而检测是否质数则需要检测小于i的数和i能否整除。这样,复杂度就是o(n*n). 2. 这么做如何?只能说这个边界太粗了,没有压缩得很仔细,还有空间可以压缩,或者说有很多重复的计算量。比如:我们检测到7是prime number,那么49呢?为了检测49是否是Prime,需要从2-48来除,看是否整除,当除到7的时候发现能整除,因此不是prime。可以明眼人,一眼就看出49不是prime,为啥要费这么大气力来一个一个尝试,因为我们知道7是因子。这就需要打破思维里的枷锁了,不用除法,因为49=7*7,所以当我们发现7是prime,那么7的所有倍数都不是prime,因此49也不是prime,完全没必要从2开始尝试。或者说,这里面思维的变化是,从做除法变成做乘法,这也是应该推广的思维方式:一个方法繁琐,通常这个方法的逆方法就很容易。比如:当我们从左侧遍历vector,发现很多case需要处理,理解起来也不方便,那么不妨让思维来个反转,从右边遍历。这个思维模式在我们习惯二分的世界里,通常可以转复杂为简洁,立竿见影! 这里写图片描述 如上图:看问题都有两个相反的角度,而通常容易看到的角度如果有很多重复运算,不简洁,很多corner case,很多不smooth的地方,很多暗沟,很多分离的地方,这是因为我们碰巧就是从一个正常的角度看见了这个问题。这个时候,就需要能转念,转念才能看到强大的normal view背后的opposite view,在那儿就没有这么繁复的,重复的操作。因此,表面看是从除法变成乘法,从被动的尝试变成主动的标记,实际上,是思维从normal view转到相反的角度看到了opposite view。因此,越是发现思维里的方法很繁复、很细琐,那就越说明这个强大的想法背后肯定藏着有一个简单的美妙的方法,这需要上升到一个信念才可以开发到背后的opposite view! 3. 回到具体的方法上去。normal view看到的是:检查每个数是否是prime,每个数的检查就是从2开始枚举看是否都不能整除,问题:每次检查都是同头检查,没有利用数与数间的关系,把连续的数看成分离的数。而opposite view看到的是:一旦发现一个prime,那么就主动去给这个prime的所有倍数标记为非prime,以后就不用再判断是否prime,这就充分利用“连续”这个特点,同时把查询变成了广播。这里特别强调:普通的查询是指所有的可能都尝试,无论别人是否尝试过,因为普通的查询来说,每次运算相互独立,并不相互通信,导致重复运算很多;广播则很好的避免了这一点:一旦掌握了某个信息,就把这个信息广播出去让相关的节点都能知道这个信息,对后面要进行的运算做标记,这个广播的作用,就是连接运算和运算之间的桥梁,防止重复计算。因此,在opposite view的角度里,存在broadcast的方法,避免了重复计算,因此复杂度从查询的o(n^2)降低到o(n)。如下图。回想一下,这个broadcast就是dp的思想。 这里写图片描述

class Solution {public: int countPrimes(int n) { // if(n<2) return 0; //vector<bool> dp(n+1,0); bool dp[n+1]={0}; int count=0,i=2; for(;i*i<=n;i++){//这里广播最多做到i*i<n即可! if(dp[i]==0){ int j=i; while(i*j<=n){//这个循环就是broadcast,把当前信息传递给后面所有相关的数。 dp[i*j]=1; j++; } count++; } } for(;i<n;i++){ if(dp[i]==0) count++; } return count; }};
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表