LeetCode 2761 和等于目标值的质数对
找出所有和为 n 的质数对
题目描述
给定一个整数 n,找出所有满足以下条件的质数对 [x, y]:
- 1 ≤ x ≤ y ≤ n
- x + y = n
- x 和 y 均为质数
返回按 x 非递减排序的二维有序列表。若不存在这样的质数对,返回空数组。
解题思路
方法选择
我们可以采用埃拉托斯特尼筛法(Sieve of Eratosthenes)来高效筛选质数,然后遍历可能的质数对进行验证。
算法步骤
- 质数筛选:使用筛法预处理出所有小于等于 n 的质数。
- 遍历验证:遍历所有可能的 x 值,检查对应的 y = n - x 是否也是质数,并且满足 x ≤ y。
代码实现
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<List<Integer>> findPrimePairs(int n) {
boolean[] isPrime = new boolean[n + 1];
for (int i = 2; i <= n; i++) {
isPrime[i] = true;
}
// 埃拉托斯特尼筛法标记非质数
for (int i = 2; i * i <= n; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
List<List<Integer>> result = new ArrayList<>();
for (int x = 2; x <= n / 2; x++) { // x最大为n/2,确保x ≤ y
int y = n - x;
if (isPrime[x] && isPrime[y]) {
result.add(List.of(x, y));
}
}
return result;
}
}
代码解释
-
初始化质数数组:
- 创建布尔数组
isPrime
,索引代表数值,初始时所有大于 1 的数标记为true
(假设是质数)。
- 创建布尔数组
-
筛法标记非质数:
- 从 2 开始遍历,若当前数是质数,则标记其所有倍数为非质数。
- 外层循环
i
从 2 到√n,内层循环j
从 i² 开始,步长为 i。
-
遍历验证质数对:
- 遍历 x 从 2 到 n/2(确保 x ≤ y),计算对应的 y = n - x。
- 若 x 和 y 均为质数,则将 [x, y] 加入结果列表。
复杂度分析
- 时间复杂度:O (n log log n),主要来自筛法的时间消耗。
- 空间复杂度:O (n),用于存储质数标记数组。
测试示例
输入:n = 10
输出:[[3, 7], [5, 5]]
解释:
- 3 + 7 = 10,且 3 和 7 均为质数。
- 5 + 5 = 10,且 5 是质数。
优化思路
在原始解法基础上,通过以下优化提升效率:
- 特殊情况处理:当 n < 4 时直接返回空列表
- 偶数预处理:提前标记所有偶数(除 2 外)为非质数
- 奇偶分离:仅处理奇数以减少循环次数
优化代码
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<List<Integer>> findPrimePairs(int n) {
// 特殊情况:n小于4时无解
if (n < 4) return new ArrayList<>();
boolean[] isPrime = new boolean[n + 1];
isPrime[2] = true; // 2是唯一的偶质数
// 预处理所有偶数(除2外)为非质数
for (int i = 4; i <= n; i += 2) {
isPrime[i] = false;
}
// 初始化所有奇数为质数
for (int i = 3; i <= n; i += 2) {
isPrime[i] = true;
}
// 埃氏筛法优化版:仅处理奇数
for (int i = 3; i * i <= n; i += 2) {
if (isPrime[i]) {
// 从i²开始,步长为2i(跳过偶数)
for (int j = i * i; j <= n; j += 2 * i) {
isPrime[j] = false;
}
}
}
// 遍历验证质数对
List<List<Integer>> result = new ArrayList<>();
for (int x = 2; x <= n / 2; x++) {
int y = n - x;
if (isPrime[x] && isPrime[y]) {
result.add(List.of(x, y));
}
}
return result;
}
}
优化点解析
1. 特殊情况处理
if (n < 4) return new ArrayList<>();
- 当 n=2 时,无法找到 x≤y 的质数对
- 当 n=3 时,可能的组合是 [2,1],但 1 不是质数
- 直接返回空列表避免后续无效计算
2. 偶数预处理
for (int i = 4; i <= n; i += 2) {
isPrime[i] = false;
}
- 除 2 外的所有偶数不可能是质数
- 直接批量标记,减少后续筛法处理量
3. 奇偶分离
for (int i = 3; i <= n; i += 2) {
isPrime[i] = true;
}
- 仅初始化奇数为质数候选
- 筛法循环步长设为 2,只处理奇数
4. 筛法优化
for (int j = i * i; j <= n; j += 2 * i)
- 从 i² 开始标记非质数(i² 是第一个未被标记的倍数)
- 步长设为 2i,跳过所有偶数倍数
复杂度对比
维度 | 原始解法 | 优化解法 |
---|---|---|
时间复杂度 | O(n log log n) | O(n log log n) |
空间复杂度 | O(n) | O(n) |
实际效率 | 常规实现 | 减少约 50% 操作量 |
测试示例
输入:n = 10
输出:[[3, 7], [5, 5]]
执行流程:
- 初始化 isPrime 数组,标记 2 为质数
- 标记 4,6,8,10 为非质数
- 筛法处理奇数 3,5,7,9:
- 3 的倍数 9 被标记为非质数
- 5 的平方 25 超过 n=10,无需处理
- 遍历 x=2 到 5:
- x=2 → y=8(非质数)
- x=3 → y=7(质数)
- x=5 → y=5(质数)
总结
埃拉托斯特尼筛法是解决质数问题的经典算法,通过预处理质数列表可以大大提升后续查询效率。在本题中,我们利用筛法快速得到质数集合,再通过遍历验证可能的质数对,最终在 O (n) 时间内完成所有验证操作。
这种方法在处理较大 n 值时依然高效,是解决类似问题的推荐方案。
通过以下优化手段,我们在保持理论时间复杂度的同时显著提升了实际运行效率:
- 提前剪枝:处理小 n 值的特殊情况
- 奇偶分离:减少 50% 的循环次数
- 倍数跳跃:更高效地标记非质数