diff --git a/interview/Print_PrimeNumbers.md b/interview/Print_PrimeNumbers.md new file mode 100644 index 0000000000000000000000000000000000000000..8b5b2ad0b76daa65e58a2d0b34791ef209bef52e --- /dev/null +++ b/interview/Print_PrimeNumbers.md @@ -0,0 +1,151 @@ +# How to find prime Numbers efficiently + +**Translator: [shazi4399](https://github.com/shazi4399)** + +**Author: [labuladong](https://github.com/labuladong)** + +The definition of a prime number seems simple,which is said to be prime number if it can be divided by 1 and itself. + +However,don't think that the definition of prime numbers is simple. I am afraid that few people can write a prime-related algorithm that works really efficiently. Let's say you write a function like this: + +```java +// Returns several primes in the interval [2, n) +int countPrimes(int n) + +// E.g. countPrimes (10) returns 4 +// Because 2,3,5,7 is prime numbers +``` + +How would you progrma this function? I think you maybe write like this: + +```java +int countPrimes(int n) { + int count = 0; + for (int i = 2; i < n; i++) + if (isPrim(i)) count++; + return count; +} + +// Determines whether integer n is prime +boolean isPrime(int n) { + for (int i = 2; i < n; i++) + if (n % i == 0) + // There are other divisibility factors + return false; + return true; +} +``` + +The time complexity is O (n ^ 2), which is a big problem.**First of all, the idea of using the isPrime function to assist is not efficient; and even if you want to use the isPrime function, there is computational redundancy in writing the algorithm**. + +Let's briefly talk about **how to write an algorithm if you want to determine whether a number is prime or not**. Just slightly modify the for loop condition in the isPrim code above: + +```java +boolean isPrime(int n) { + for (int i = 2; i * i <= n; i++) + ... +} +``` + +In other words, `i` does not need to traverse to` n`, but only to `sqrt (n)`. Why? let's take an example, suppose `n = 12`. + +```java +12 = 2 × 6 +12 = 3 × 4 +12 = sqrt(12) × sqrt(12) +12 = 4 × 3 +12 = 6 × 2 +``` + +As you can see, the last two products are the reverse of the previous two, and the critical point of inversion is at `sqrt (n)`. + +In other words, if no divisible factor is found within the interval `[[2, sqrt (n)]`, you can directly conclude that `n` is a prime number, because in the interval `[[sqrt (n), n] ` Nor will you find a divisible factor. + +Now, the time complexity of the `isPrime` function is reduced to O (sqrt (N)), ** but we don't actually need this function to implement the` countPrimes` function. The above just hope that readers understand the meaning of `sqrt (n)`, because it will be used again later. + + +### Efficient implementation `countPrimes` + +The core idea of efficiently solving this problem is to reverse the conventional idea above: + +First from 2, we know that 2 is a prime number, then 2 × 2 = 4, 3 × 2 = 6, 4 × 2 = 8 ... all are not prime numbers. + +Then we found that 3 is also a prime number, so 3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12 ... are also impossible to be prime numbers. + +Seeing this, do you understand the logic of this exclusion method a bit? First look at our first version of the code: + +```java +int countPrimes(int n) { + boolean[] isPrim = new boolean[n]; + // Initialize the arrays to true + Arrays.fill(isPrim, true); + + for (int i = 2; i < n; i++) + if (isPrim[i]) + // Multiples of i cannot be prime + for (int j = 2 * i; j < n; j += i) + isPrim[j] = false; + + int count = 0; + for (int i = 2; i < n; i++) + if (isPrim[i]) count++; + + return count; +} +``` + +If you can understand the above code, then you have mastered the overall idea, but there are two subtle areas that can be optimized. + +First of all, recall the `isPrime` function that just judges whether a number is prime. Due to the symmetry of the factors, the for loop only needs to traverse` [2, sqrt (n)] `. Here is similar, our outer for loop only needs to traverse to `sqrt (n)`: + +```java +for (int i = 2; i * i < n; i++) + if (isPrim[i]) + ... +``` + +In addition, it is difficult to notice that the inner for loop can also be optimized. Our previous approach was: + +```java +for (int j = 2 * i; j < n; j += i) + isPrim[j] = false; +``` + +This can mark all integer multiples of `i` as` false`, but there is still computational redundancy. + +For example, when `n = 25` and` i = 4`, the algorithm will mark numbers such as 4 × 2 = 8, 4 × 3 = 12, and so on, but these two numbers have been marked by 2 × 4 and 3 × 4 that is `i = 2` and` i = 3`. + +We can optimize it slightly so that `j` traverses from the square of` i` instead of starting from `2 * i`: + +```java +for (int j = i * i; j < n; j += i) + isPrim[j] = false; +``` + +In this way, the algorithm for counting prime numbers is efficiently implemented. In fact, this algorithm has a name, which called Sieve of Eratosthenes. Take a look at the complete final code: + +```java +int countPrimes(int n) { + boolean[] isPrim = new boolean[n]; + Arrays.fill(isPrim, true); + for (int i = 2; i * i < n; i++) + if (isPrim[i]) + for (int j = i * i; j < n; j += i) + isPrim[j] = false; + + int count = 0; + for (int i = 2; i < n; i++) + if (isPrim[i]) count++; + + return count; +} +``` + +**The time complexity of this algorithm is difficult to calculate**.It is obvious that the time is related to these two nested for loops. The operands should be: + + n/2 + n/3 + n/5 + n/7 + ... += n × (1/2 + 1/3 + 1/5 + 1/7...) + +In parentheses, ther is the inverse of the prime number .The final result is O(N * loglogN),and readers interested in this can refer to the time complexity of the algorithm + +That is all about how to find prime Numbers.The seemingly simple problem does has a lot of details to polish \ No newline at end of file diff --git "a/interview/\346\211\223\345\215\260\347\264\240\346\225\260.md" "b/interview/\346\211\223\345\215\260\347\264\240\346\225\260.md" deleted file mode 100644 index 3875915ade14f51fe8f9b0930723b626d2401be8..0000000000000000000000000000000000000000 --- "a/interview/\346\211\223\345\215\260\347\264\240\346\225\260.md" +++ /dev/null @@ -1,151 +0,0 @@ -# 如何高效寻找素数 - -素数的定义看起来很简单,如果一个数如果只能被 1 和它本身整除,那么这个数就是素数。 - -不要觉得素数的定义简单,恐怕没多少人真的能把素数相关的算法写得高效。比如让你写这样一个函数: - -```java -// 返回区间 [2, n) 中有几个素数 -int countPrimes(int n) - -// 比如 countPrimes(10) 返回 4 -// 因为 2,3,5,7 是素数 -``` - -你会如何写这个函数?我想大家应该会这样写: - -```java -int countPrimes(int n) { - int count = 0; - for (int i = 2; i < n; i++) - if (isPrim(i)) count++; - return count; -} - -// 判断整数 n 是否是素数 -boolean isPrime(int n) { - for (int i = 2; i < n; i++) - if (n % i == 0) - // 有其他整除因子 - return false; - return true; -} -``` - -这样写的话时间复杂度 O(n^2),问题很大。**首先你用 isPrime 函数来辅助的思路就不够高效;而且就算你要用 isPrime 函数,这样写算法也是存在计算冗余的**。 - -先来简单说下**如果你要判断一个数是不是素数,应该如何写算法**。只需稍微修改一下上面的 isPrim 代码中的 for 循环条件: - -```java -boolean isPrime(int n) { - for (int i = 2; i * i <= n; i++) - ... -} -``` - -换句话说,`i` 不需要遍历到 `n`,而只需要到 `sqrt(n)` 即可。为什么呢,我们举个例子,假设 `n = 12`。 - -```java -12 = 2 × 6 -12 = 3 × 4 -12 = sqrt(12) × sqrt(12) -12 = 4 × 3 -12 = 6 × 2 -``` - -可以看到,后两个乘积就是前面两个反过来,反转临界点就在 `sqrt(n)`。 - -换句话说,如果在 `[2,sqrt(n)]` 这个区间之内没有发现可整除因子,就可以直接断定 `n` 是素数了,因为在区间 `[sqrt(n),n]` 也一定不会发现可整除因子。 - -现在,`isPrime` 函数的时间复杂度降为 O(sqrt(N)),**但是我们实现 `countPrimes` 函数其实并不需要这个函数**,以上只是希望读者明白 `sqrt(n)` 的含义,因为等会还会用到。 - - -### 高效实现 `countPrimes` - -高效解决这个问题的核心思路是和上面的常规思路反着来: - -首先从 2 开始,我们知道 2 是一个素数,那么 2 × 2 = 4, 3 × 2 = 6, 4 × 2 = 8... 都不可能是素数了。 - -然后我们发现 3 也是素数,那么 3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12... 也都不可能是素数了。 - -看到这里,你是否有点明白这个排除法的逻辑了呢?先看我们的第一版代码: - -```java -int countPrimes(int n) { - boolean[] isPrim = new boolean[n]; - // 将数组都初始化为 true - Arrays.fill(isPrim, true); - - for (int i = 2; i < n; i++) - if (isPrim[i]) - // i 的倍数不可能是素数了 - for (int j = 2 * i; j < n; j += i) - isPrim[j] = false; - - int count = 0; - for (int i = 2; i < n; i++) - if (isPrim[i]) count++; - - return count; -} -``` - -如果上面这段代码你能够理解,那么你已经掌握了整体思路,但是还有两个细微的地方可以优化。 - -首先,回想刚才判断一个数是否是素数的 `isPrime` 函数,由于因子的对称性,其中的 for 循环只需要遍历 `[2,sqrt(n)]` 就够了。这里也是类似的,我们外层的 for 循环也只需要遍历到 `sqrt(n)`: - -```java -for (int i = 2; i * i < n; i++) - if (isPrim[i]) - ... -``` - -除此之外,很难注意到内层的 for 循环也可以优化。我们之前的做法是: - -```java -for (int j = 2 * i; j < n; j += i) - isPrim[j] = false; -``` - -这样可以把 `i` 的整数倍都标记为 `false`,但是仍然存在计算冗余。 - -比如 `n = 25`,`i = 4` 时算法会标记 4 × 2 = 8,4 × 3 = 12 等等数字,但是这两个数字已经被 `i = 2` 和 `i = 3` 的 2 × 4 和 3 × 4 标记了。 - -我们可以稍微优化一下,让 `j` 从 `i` 的平方开始遍历,而不是从 `2 * i` 开始: - -```java -for (int j = i * i; j < n; j += i) - isPrim[j] = false; -``` - -这样,素数计数的算法就高效实现了,其实这个算法有一个名字,叫做 Sieve of Eratosthenes。看下完整的最终代码: - -```java -int countPrimes(int n) { - boolean[] isPrim = new boolean[n]; - Arrays.fill(isPrim, true); - for (int i = 2; i * i < n; i++) - if (isPrim[i]) - for (int j = i * i; j < n; j += i) - isPrim[j] = false; - - int count = 0; - for (int i = 2; i < n; i++) - if (isPrim[i]) count++; - - return count; -} -``` - -**该算法的时间复杂度比较难算**,显然时间跟这两个嵌套的 for 循环有关,其操作数应该是: - - n/2 + n/3 + n/5 + n/7 + ... -= n × (1/2 + 1/3 + 1/5 + 1/7...) - -括号中是素数的倒数。其最终结果是 O(N * loglogN),有兴趣的读者可以查一下该算法的时间复杂度证明。 - -以上就是素数算法相关的全部内容。怎么样,是不是看似简单的问题却有不少细节可以打磨呀? - -**致力于把算法讲清楚!欢迎关注我的微信公众号 labuladong,查看更多通俗易懂的文章**: - -![labuladong](../pictures/labuladong.png) \ No newline at end of file