给你一个下标从 0 开始、长度为 n 的整数数组 nums 和一个整数 k ,返回满足下述条件的下标对 (i, j) 的数目:
0 <= i < j <= n - 1 且
nums[i] * nums[j] 能被 k 整除。
示例 1:
输入:nums = [1,2,3,4,5], k = 2
输出:7
解释:
共有 7 对下标的对应积可以被 2 整除:
(0, 1)、(0, 3)、(1, 2)、(1, 3)、(1, 4)、(2, 3) 和 (3, 4)
它们的积分别是 2、4、6、8、10、12 和 20 。
其他下标对,例如 (0, 2) 和 (2, 4) 的乘积分别是 3 和 15 ,都无法被 2 整除。
示例 2:
输入:nums = [1,2,3,4], k = 5
输出:0
解释:不存在对应积可以被 5 整除的下标对。
提示:
1 <= nums.length <= 10^5
1 <= nums[i], k <= 10^5
思路:比较直观的做法是采用两层for循环枚举对数,但O(n^2)的复杂度必然造成超时。考虑能够整除k的数满足的性质,当x可以整除k时,则x的因数必包含k的所有因数,即:
gcd(x, k) = k (gcd为最大公因数)
从中可以发现nums[i]*nums[j]%k=0可以转化为找到两个元素nums[i]和nums[j]使得:
gcd(nums[i], k) * gcd(nums[j], k) = 0
我们令gcd(nums[i], k) 为x,gcd(nums[j], k) 为y:
因此我们只需事先求出所有元素与k的最大公因数,并统计出现的次数,之后借助map以双层for遍历x和y 即可。由于x和y为k的因数,因此两层for循环的复杂度为O(k),而总的时间复杂度为O(nlogn + k)。
class Solution {
public:
int gcd(int a, int b) {
if (b == 0)
return a;
return gcd(b, a % b);
}
long long countPairs(vector<int>& nums, int k) {
unordered_map<int, int> sum;
for (int num : nums)
++sum[gcd(num, k)];
long long ans = 0;
for (auto&& [x, sumX] : sum)
for (auto&& [y, sumY] : sum)
if (static_cast<long long>(x) * y % k == 0)
ans += static_cast<long long>(sumX) * sumY;
for (int num : nums)
if (static_cast<long long>(num) * num % k == 0)
--ans;
return ans / 2;
}
};