給定非負整數 n,回傳小於 n 的質數數量。n 可達 10^6 量級,暴力試除法會超時,需使用篩法優化。
核心觀察:判斷一數是否為質數,只需檢查能否被比它小的質數整除。
int countPrimes(int n) {
if (n <= 2) return 0;
if (n == 3) return 1;
int primes[200] = {0}; // 假設 n < 10^6,質數個數 < 200 足夠
int k = 0, count = 0;
primes[k++] = 2; count = 1; // 2 是第一個質數
for (int i = 3; i < n; i++) {
bool isPrime = true;
for (int j = 0; j < k; j++) {
if (i % primes[j] == 0) { isPrime = false; break; }
}
if (isPrime) {
if (k < 200) primes[k++] = i;
count++;
i++; // 關鍵優化:質數後的偶數必非質數 (除 2,3 外),直接跳過
}
}
return count;
}
時間複雜度約 O(n√n / log n),對中等 n 可通過,但非最優。
時間複雜度 O(n log log n),空間 O(n),百萬級 n 毫秒級完成。
int countPrimes(int n) {
if (n <= 2) return 0;
bool *isComposite = (bool*)calloc(n, sizeof(bool)); // 全部初始為 false (質數)
int count = 0;
for (int i = 2; i * i < n; i++) { // 只需篩到 √n
if (!isComposite[i]) {
for (int j = i * i; j < n; j += i) // 從 i*i 開始標記
isComposite[j] = true;
}
}
for (int i = 2; i < n; i++)
if (!isComposite[i]) count++;
free(isComposite);
return count;
}
char 或位圖 (bit array) 降低記憶體 8 倍每個合數只被其最小質因數標記一次,彻底避免重複標記:
int countPrimes(int n) {
vector<int> primes;
vector<bool> isComposite(n, false);
for (int i = 2; i < n; i++) {
if (!isComposite[i]) primes.push_back(i);
for (int p : primes) {
if (i * p >= n) break;
isComposite[i * p] = true;
if (i % p == 0) break; // 核心:p 是 i 的最小質因數
}
}
return primes.size();
}
| 演算法 | 時間複雜度 | 空間複雜度 | n=10^6 耗時 |
|---|---|---|---|
| 試除法優化 | ~O(n^1.5) | O(√n) | ~秒級 |
| 埃氏篩法 | O(n log log n) | O(n) | ~5-10ms |
| 線性篩 | O(n) | O(n) | ~3-5ms |
| 位圖優化埃氏篩 | O(n log log n) | O(n/8) | ~2-3ms |
i*i 與 √n 優化原理