1. 题目解析与核心思路
这道题目要求计算给定数组中所有公倍数对的和。具体来说,对于数组中的每一对元素(a_i, a_j),如果存在某个整数k使得a_i和a_j都是k的倍数,那么就将这对元素的值相加,最终返回所有这些符合条件的对的和。
1.1 问题重述
给定一个整数数组nums,我们需要找出所有满足以下条件的数对(i,j):
- i < j
- 存在某个整数k,使得nums[i] % k == 0且nums[j] % k == 0
然后计算所有这些数对的nums[i] + nums[j]之和。
1.2 关键点分析
要解决这个问题,我们需要考虑以下几个关键点:
- 如何高效判断两个数是否存在公倍数
- 如何避免重复计算
- 如何优化算法的时间复杂度
实际上,任何两个数都存在无限多个公倍数(比如它们的乘积就是一个公倍数),所以题目可能隐含的意思是"存在大于1的公因数",即两个数不互质。基于这个理解,我们可以将问题转化为:找出所有数对,它们的最大公约数(GCD)大于1。
2. 算法设计与优化
2.1 暴力解法分析
最直观的解法是双重循环遍历所有数对,计算每对数的GCD,如果大于1则累加它们的和:
python复制def sum_of_pairs(nums):
total = 0
n = len(nums)
for i in range(n):
for j in range(i+1, n):
if gcd(nums[i], nums[j]) > 1:
total += nums[i] + nums[j]
return total
这种方法的时间复杂度是O(n^2 * logM),其中M是数组中最大的数。对于大规模数据效率不高。
2.2 优化思路
我们可以利用数论中的一些性质来优化:
- 任何数都可以分解为质因数的乘积
- 如果两个数共享至少一个质因数,那么它们的GCD大于1
- 我们可以先预处理每个数的质因数,然后统计每个质因数对应的数的和及数量
2.3 基于质因数分解的算法
具体步骤如下:
- 对数组中的每个数进行质因数分解
- 建立一个字典,记录每个质因数对应的数的和及出现次数
- 对于每个质因数p,计算其对最终结果的贡献
- 使用容斥原理避免重复计算
3. 代码实现与解析
3.1 Java实现
java复制import java.util.*;
public class Solution {
public int sumOfPairs(int[] nums) {
Map<Integer, int[]> primeMap = new HashMap<>();
for (int num : nums) {
Set<Integer> primes = getPrimeFactors(num);
for (int p : primes) {
if (!primeMap.containsKey(p)) {
primeMap.put(p, new int[]{0, 0});
}
int[] pair = primeMap.get(p);
pair[0] += num; // sum
pair[1] += 1; // count
}
}
int total = 0;
for (int[] pair : primeMap.values()) {
int sum = pair[0];
int count = pair[1];
total += sum * (count - 1);
}
return total;
}
private Set<Integer> getPrimeFactors(int n) {
Set<Integer> factors = new HashSet<>();
if (n == 1) return factors;
for (int i = 2; i * i <= n; i++) {
while (n % i == 0) {
factors.add(i);
n /= i;
}
}
if (n > 1) {
factors.add(n);
}
return factors;
}
}
3.2 C++实现
cpp复制#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
class Solution {
public:
int sumOfPairs(vector<int>& nums) {
unordered_map<int, pair<int, int>> primeMap;
for (int num : nums) {
unordered_set<int> primes = getPrimeFactors(num);
for (int p : primes) {
auto it = primeMap.find(p);
if (it == primeMap.end()) {
primeMap[p] = {0, 0};
}
primeMap[p].first += num; // sum
primeMap[p].second += 1; // count
}
}
int total = 0;
for (auto& entry : primeMap) {
int sum = entry.second.first;
int count = entry.second.second;
total += sum * (count - 1);
}
return total;
}
private:
unordered_set<int> getPrimeFactors(int n) {
unordered_set<int> factors;
if (n == 1) return factors;
for (int i = 2; i * i <= n; i++) {
while (n % i == 0) {
factors.insert(i);
n /= i;
}
}
if (n > 1) {
factors.insert(n);
}
return factors;
}
};
3.3 Python实现
python复制from math import gcd
from collections import defaultdict
def sum_of_pairs(nums):
prime_map = defaultdict(lambda: [0, 0]) # [sum, count]
for num in nums:
primes = get_prime_factors(num)
for p in primes:
prime_map[p][0] += num
prime_map[p][1] += 1
total = 0
for sum_val, count in prime_map.values():
total += sum_val * (count - 1)
return total
def get_prime_factors(n):
factors = set()
if n == 1:
return factors
i = 2
while i * i <= n:
while n % i == 0:
factors.add(i)
n = n // i
i += 1
if n > 1:
factors.add(n)
return factors
4. 算法复杂度分析
4.1 时间复杂度
- 质因数分解部分:对于每个数n,分解质因数的时间复杂度是O(√n)
- 总体时间复杂度:O(N * √M),其中N是数组长度,M是数组中最大的数
- 相比暴力解法的O(N^2)有了显著提升
4.2 空间复杂度
- 需要存储每个质因数对应的和与计数
- 最坏情况下空间复杂度是O(N * logM),因为一个数最多有O(logM)个不同的质因数
5. 测试用例与验证
5.1 基础测试用例
python复制assert sum_of_pairs([2, 3, 5]) == 0
assert sum_of_pairs([2, 4, 6]) == 18 # (2,4)=6, (2,6)=8, (4,6)=10
assert sum_of_pairs([1, 2, 3]) == 0
5.2 边界测试用例
python复制assert sum_of_pairs([]) == 0
assert sum_of_pairs([1]) == 0
assert sum_of_pairs([1, 1, 1]) == 0
assert sum_of_pairs([2, 2, 2]) == 12 # 3 pairs, each sum is 4
5.3 性能测试用例
python复制# 大数组测试
large_array = [i for i in range(2, 10001)]
# 验证是否能快速处理
sum_of_pairs(large_array)
6. 常见问题与优化技巧
6.1 质因数分解优化
- 预处理最小质因子表可以加速质因数分解
- 使用Pollard's Rho算法处理大数分解
6.2 重复计算问题
- 当数组中有重复数字时,可以预先统计每个数的出现次数
- 避免对相同数字重复进行质因数分解
6.3 特殊情况的处理
- 数字1需要特殊处理,因为它没有质因数
- 质数可以快速判断,因为它们只有自身一个质因数
7. 实际应用场景
这类算法问题在实际中有多种应用:
- 数据聚类分析:找出具有共同特征的数据点
- 网络分析:识别共享属性的节点对
- 推荐系统:发现用户或物品之间的潜在关联
8. 扩展思考
- 如果题目改为求公倍数对的数量而非和,如何修改算法?
- 如果要求三个数的公倍数组而非两个数的对,算法该如何调整?
- 如何并行化这个算法以处理超大规模数据?
9. 面试技巧
在面试中遇到这类问题时:
- 先明确问题要求,确认边界条件
- 从暴力解法开始,逐步优化
- 讨论时间复杂度和空间复杂度
- 考虑特殊情况和大数据量时的处理方式
- 写出清晰可读的代码,添加必要注释
