2523-范围内最接近的两个质数

Raphael Liu Lv10

给你两个正整数 leftright ,请你找到两个整数 num1num2 ,它们满足:

  • left <= nums1 < nums2 <= right
  • nums1nums2 都是 质数
  • nums2 - nums1 是满足上述条件的质数对中的 最小值

请你返回正整数数组 ans = [nums1, nums2] 。如果有多个整数对满足上述条件,请你返回 nums1
最小的质数对。如果不存在符合题意的质数对,请你返回 [-1, -1]

如果一个整数大于 1 ,且只能被 1 和它自己整除,那么它是一个质数。

示例 1:

**输入:** left = 10, right = 19
**输出:** [11,13]
**解释:** 10 到 19 之间的质数为 11 ,13 ,17 和 19 。
质数对的最小差值是 2 ,[11,13] 和 [17,19] 都可以得到最小差值。
由于 11 比 17 小,我们返回第一个质数对。

示例 2:

**输入:** left = 4, right = 6
**输出:** [-1,-1]
**解释:** 给定范围内只有一个质数,所以题目条件无法被满足。

提示:

  • 1 <= left <= right <= 106

分两步:

  1. 筛质数,做法见 204. 计数质数
  2. 找 [\textit{left},\textit{right}] 范围内的最小质数间隙(prime gap),暴力枚举范围内的所有相邻质数即可。

附:视频讲解

优化

  1. 找范围内的第一个质数可以用二分查找。
  2. 可以往质数表末尾额外插入 2 个 10^6+1,这样无需判断下标是否越界。
[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MX = 10 ** 6 + 1
primes = []
is_prime = [True] * MX
for i in range(2, MX):
if is_prime[i]:
primes.append(i)
for j in range(i * i, MX, i):
is_prime[j] = False
primes.extend((MX, MX)) # 保证下面下标不会越界

class Solution:
def closestPrimes(self, left: int, right: int) -> List[int]:
p = q = -1
i = bisect_left(primes, left)
while primes[i + 1] <= right:
if p < 0 or primes[i + 1] - primes[i] < q - p:
p, q = primes[i], primes[i + 1]
i += 1
return [p, q]
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
private final static int MX = (int) 1e6;
private final static int[] primes = new int[78500];

static {
var np = new boolean[MX + 1];
var pi = 0;
for (var i = 2; i <= MX; ++i)
if (!np[i]) {
primes[pi++] = i;
for (var j = i; j <= MX / i; ++j) // 避免溢出的写法
np[i * j] = true;
}
primes[pi++] = MX + 1;
primes[pi++] = MX + 1; // 保证下面下标不会越界
}

public int[] closestPrimes(int left, int right) {
int p = -1, q = -1;
for (var i = lowerBound(primes, left); primes[i + 1] <= right; ++i)
if (p < 0 || primes[i + 1] - primes[i] < q - p) {
p = primes[i];
q = primes[i + 1];
}
return new int[]{p, q};
}

// 见 https://www.bilibili.com/video/BV1AP41137w7/
private int lowerBound(int[] nums, int target) {
int left = -1, right = nums.length; // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
// 循环不变量:
// nums[left] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid; // 范围缩小到 (mid, right)
else
right = mid; // 范围缩小到 (left, mid)
}
return right;
}
}
[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const int MX = 1e6;
vector<int> primes;

int init = []() {
bool np[MX + 1]{};
for (int i = 2; i <= MX; ++i)
if (!np[i]) {
primes.push_back(i);
for (int j = i; j <= MX / i; ++j) // 避免溢出的写法
np[i * j] = true;
}
primes.push_back(MX + 1);
primes.push_back(MX + 1); // 保证下面下标不会越界
return 0;
}();

class Solution {
public:
vector<int> closestPrimes(int left, int right) {
int p = -1, q = -1;
int i = lower_bound(primes.begin(), primes.end(), left) - primes.begin();
for (; primes[i + 1] <= right; ++i)
if (p < 0 || primes[i + 1] - primes[i] < q - p) {
p = primes[i];
q = primes[i + 1];
}
return {p, q};
}
};
[sol1-Go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const mx = 1e6 + 1
var primes = make([]int, 0, 78500)

func init() {
np := [mx]bool{}
for i := 2; i < mx; i++ {
if !np[i] {
primes = append(primes, i)
for j := i * i; j < mx; j += i {
np[j] = true
}
}
}
primes = append(primes, mx, mx) // 保证下面下标不会越界
}

func closestPrimes(left, right int) []int {
p, q := -1, -1
for i := sort.SearchInts(primes, left); primes[i+1] <= right; i++ {
if p < 0 || primes[i+1]-primes[i] < q-p {
p, q = primes[i], primes[i+1]
}
}
return []int{p, q}
}

也可以用线性筛(欧拉筛)做,具体原理见 视频讲解

[sol2-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MX = 10 ** 6 + 1
primes = []
is_prime = [True] * MX
for i in range(2, MX):
if is_prime[i]:
primes.append(i)
for p in primes:
if i * p >= MX: break
is_prime[i * p] = False
if i % p == 0: break
primes.extend((MX, MX)) # 保证下面下标不会越界

class Solution:
def closestPrimes(self, left: int, right: int) -> List[int]:
p = q = -1
i = bisect_left(primes, left)
while primes[i + 1] <= right:
if p < 0 or primes[i + 1] - primes[i] < q - p:
p, q = primes[i], primes[i + 1]
i += 1
return [p, q]
[sol2-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solution {
private final static int MX = (int) 1e6;
private final static int[] primes = new int[78500];

static {
var np = new boolean[MX + 1];
var pi = 0;
for (var i = 2; i <= MX; ++i) {
if (!np[i]) primes[pi++] = i;
for (var j = 0; j < pi; ++j) {
var p = primes[j];
if (i * p > MX) break;
np[i * p] = true;
if (i % p == 0) break;
}
}
primes[pi++] = MX + 1;
primes[pi++] = MX + 1; // 保证下面下标不会越界
}

public int[] closestPrimes(int left, int right) {
int p = -1, q = -1;
for (var i = lowerBound(primes, left); primes[i + 1] <= right; ++i)
if (p < 0 || primes[i + 1] - primes[i] < q - p) {
p = primes[i];
q = primes[i + 1];
}
return new int[]{p, q};
}

// 见 https://www.bilibili.com/video/BV1AP41137w7/
private int lowerBound(int[] nums, int target) {
int left = -1, right = nums.length; // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
// 循环不变量:
// nums[left] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid; // 范围缩小到 (mid, right)
else
right = mid; // 范围缩小到 (left, mid)
}
return right;
}
}
[sol2-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const int MX = 1e6;
vector<int> primes;

int init = []() {
bool np[MX + 1]{};
for (int i = 2; i <= MX; ++i) {
if (!np[i]) primes.push_back(i);
for (int p: primes) {
if (i * p > MX) break;
np[i * p] = true;
if (i % p == 0) break;
}
}
primes.push_back(MX + 1);
primes.push_back(MX + 1); // 保证下面下标不会越界
return 0;
}();

class Solution {
public:
vector<int> closestPrimes(int left, int right) {
int p = -1, q = -1;
int i = lower_bound(primes.begin(), primes.end(), left) - primes.begin();
for (; primes[i + 1] <= right; ++i)
if (p < 0 || primes[i + 1] - primes[i] < q - p) {
p = primes[i];
q = primes[i + 1];
}
return {p, q};
}
};
[sol2-Go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const mx = 1e6 + 1
var primes = make([]int, 0, 78500)

func init() {
np := [mx]bool{}
for i := 2; i < mx; i++ {
if !np[i] {
primes = append(primes, i)
}
for _, p := range primes {
if i*p >= mx {
break
}
np[i*p] = true
if i%p == 0 {
break
}
}
}
primes = append(primes, mx, mx) // 保证下面下标不会越界
}

func closestPrimes(left, right int) []int {
p, q := -1, -1
for i := sort.SearchInts(primes, left); primes[i+1] <= right; i++ {
if p < 0 || primes[i+1]-primes[i] < q-p {
p, q = primes[i], primes[i+1]
}
}
return []int{p, q}
}

复杂度分析

  • 时间复杂度:O(\textit{right}),忽略预处理的时间复杂度。严谨地说,由于范围内有 O\left(\dfrac{\textit{right} }{\log\textit{right} }-\dfrac{\textit{left} }{\log\textit{left} }\right) 个质数(根据质数密度),所以遍历的时间复杂度为 O\left(\dfrac{\textit{right} }{\log\textit{right} }-\dfrac{\textit{left} }{\log\textit{left} }\right),再算上二分质数的时间 O(\log\pi(U))(\pi(U) 表示 U=10^6 内的质数个数,这有 78498 个),总的时间复杂度为 O\left(\log\pi(U) + \dfrac{\textit{right} }{\log\textit{right} }-\dfrac{\textit{left} }{\log\textit{left} }\right)。
  • 空间复杂度:O(1),忽略预处理的空间复杂度。仅用到若干变量。
 Comments
On this page
2523-范围内最接近的两个质数