Medium
题目描述
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 nums 的子数组中元素的最大公约数等于 k 的子数组数目。
子数组 是数组中一个连续、非空的元素序列。
数组的 最大公约数 是能整除数组中所有元素的最大正整数。
示例 1:
输入:nums = [9,3,1,2,6,3], k = 3
输出:4
解释:nums 的子数组中,满足元素的最大公约数等于 3 的子数组如下:
- [9,3,1,2,6,3]
- [9,3,1,2,6,3]
- [9,3,1,2,6,3]
- [9,3,1,2,6,3]
示例 2:
输入:nums = [4], k = 7
输出:0
解释:不存在满足元素的最大公约数等于 7 的子数组。
提示:
1 <= nums.length <= 10001 <= nums[i], k <= 10^9
解题思路
这道题要求找到所有最大公约数等于 k 的子数组数目。由于数组长度较小(最多 1000),我们可以采用暴力枚举的方法。
解题思路:
暴力枚举:枚举所有可能的子数组,计算每个子数组的最大公约数,统计等于 k 的数量。
优化的枚举:对于每个起始位置 i,我们从 i 开始向右扩展子数组。关键观察是,当我们从位置 i 到 j 的子数组的最大公约数已经小于 k 时,继续扩展也不可能得到等于 k 的最大公约数,因为最大公约数只会越来越小。
早期剪枝:
- 如果当前元素不能被 k 整除,那么包含这个元素的任何子数组的最大公约数都不可能是 k
- 如果当前子数组的最大公约数已经小于 k,则停止扩展
算法步骤:
- 遍历每个可能的起始位置 i
- 从位置 i 开始,逐步扩展子数组到位置 j
- 计算当前子数组的最大公约数
- 如果等于 k,计数器加1
- 如果小于 k,停止扩展(剪枝)
- 返回总计数
时间复杂度主要由计算最大公约数决定,使用欧几里得算法可以高效计算。
代码实现
class Solution {
public:
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int subarrayGCD(vector<int>& nums, int k) {
int n = nums.size();
int count = 0;
for (int i = 0; i < n; i++) {
if (nums[i] % k != 0) continue; // 剪枝:如果当前数不能被k整除
int currentGCD = 0;
for (int j = i; j < n; j++) {
if (nums[j] % k != 0) break; // 剪枝:如果当前数不能被k整除
currentGCD = gcd(currentGCD, nums[j]);
if (currentGCD == k) {
count++;
} else if (currentGCD < k) {
break; // 剪枝:GCD已经小于k,不可能再等于k
}
}
}
return count;
}
};
class Solution:
def subarrayGCD(self, nums: List[int], k: int) -> int:
import math
n = len(nums)
count = 0
for i in range(n):
if nums[i] % k != 0: # 剪枝:如果当前数不能被k整除
continue
current_gcd = 0
for j in range(i, n):
if nums[j] % k != 0: # 剪枝:如果当前数不能被k整除
break
current_gcd = math.gcd(current_gcd, nums[j])
if current_gcd == k:
count += 1
elif current_gcd < k:
break # 剪枝:GCD已经小于k,不可能再等于k
return count
public class Solution {
private int GCD(int a, int b) {
return b == 0 ? a : GCD(b, a % b);
}
public int SubarrayGCD(int[] nums, int k) {
int n = nums.Length;
int count = 0;
for (int i = 0; i < n; i++) {
if (nums[i] % k != 0) continue; // 剪枝:如果当前数不能被k整除
int currentGCD = 0;
for (int j = i; j < n; j++) {
if (nums[j] % k != 0) break; // 剪枝:如果当前数不能被k整除
currentGCD = GCD(currentGCD, nums[j]);
if (currentGCD == k) {
count++;
} else if (currentGCD < k) {
break; // 剪枝:GCD已经小于k,不可能再等于k
}
}
}
return count;
}
}
var subarrayGCD = function(nums, k) {
function gcd(a, b) {
while (b !== 0) {
let temp = b;
b = a % b;
a = temp;
}
return a;
}
let count = 0;
for (let i = 0; i < nums.length; i++) {
let currentGCD = nums[i];
for (let j = i; j < nums.length; j++) {
currentGCD = gcd(currentGCD, nums[j]);
if (currentGCD === k) {
count++;
} else if (currentGCD < k) {
break;
}
}
}
return count;
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n² × log(max(nums))) |
| 空间复杂度 | O(1) |
时间复杂度分析:
- 外层循环:O(n)
- 内层循环:最坏情况 O(n)
- GCD 计算:O(log(max(nums)))
- 由于剪枝优化,实际运行时间会更好
空间复杂度分析:
- 只使用了常数个额外变量