Medium
题目描述
给你一个整数数组 nums 和一个整数 k,请你统计并返回 nums 的子数组中满足 元素最小公倍数为 k 的子数组数目。
子数组 是数组中一个连续非空的元素序列。
数组的 最小公倍数 是可被数组中所有元素整除的最小正整数。
示例 1:
输入:nums = [3,6,2,7,1], k = 6
输出:4
解释:以 6 为最小公倍数的子数组是:
- [3,6] 从下标 0 到下标 1
- [6] 从下标 1 到下标 1
- [3,6,2] 从下标 0 到下标 2
- [6,2] 从下标 1 到下标 2
示例 2:
输入:nums = [3], k = 2
输出:0
解释:不存在以 2 为最小公倍数的子数组。
提示:
1 <= nums.length <= 10001 <= nums[i], k <= 1000
解题思路
解题思路
这道题要求找出所有最小公倍数等于 k 的连续子数组个数。由于数组长度较小(≤1000),我们可以使用暴力枚举的方法。
核心观察:
- 最小公倍数(LCM)具有单调性:随着元素增加,LCM 只会增大或保持不变
- 一旦某个子数组的 LCM 超过 k,那么包含这个子数组的更长子数组的 LCM 也不可能等于 k
- 如果某个元素不能整除 k,那么包含它的任何子数组的 LCM 都不可能等于 k
算法步骤:
- 枚举所有可能的子数组起始位置 i
- 从位置 i 开始,逐步扩展子数组终止位置 j
- 维护当前子数组的 LCM,如果等于 k 则计数加一
- 如果 LCM 超过 k,提前终止内层循环(剪枝优化)
优化技巧:
- 使用
gcd(a,b) = a*b/lcm(a,b)和欧几里得算法快速计算 LCM - 当 LCM > k 时立即跳出内层循环
- 预先检查元素是否为 k 的因子,快速过滤无效情况
时间复杂度为 O(n²·log(max(nums))),空间复杂度为 O(1)。
代码实现
class Solution {
public:
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int lcm(int a, int b) {
return a / gcd(a, b) * b;
}
int subarrayLCM(vector<int>& nums, int k) {
int n = nums.size();
int count = 0;
for (int i = 0; i < n; i++) {
int currentLCM = 0;
for (int j = i; j < n; j++) {
if (currentLCM == 0) {
currentLCM = nums[j];
} else {
currentLCM = lcm(currentLCM, nums[j]);
}
if (currentLCM == k) {
count++;
} else if (currentLCM > k) {
break; // 剪枝:LCM已经超过k,不需要继续扩展
}
}
}
return count;
}
};
class Solution:
def subarrayLCM(self, nums: List[int], k: int) -> int:
def gcd(a, b):
while b:
a, b = b, a % b
return a
def lcm(a, b):
return a * b // gcd(a, b)
n = len(nums)
count = 0
for i in range(n):
current_lcm = 0
for j in range(i, n):
if current_lcm == 0:
current_lcm = nums[j]
else:
current_lcm = lcm(current_lcm, nums[j])
if current_lcm == k:
count += 1
elif current_lcm > k:
break # 剪枝:LCM已经超过k,不需要继续扩展
return count
public class Solution {
private int Gcd(int a, int b) {
return b == 0 ? a : Gcd(b, a % b);
}
private int Lcm(int a, int b) {
return a / Gcd(a, b) * b;
}
public int SubarrayLCM(int[] nums, int k) {
int n = nums.Length;
int count = 0;
for (int i = 0; i < n; i++) {
int currentLCM = 0;
for (int j = i; j < n; j++) {
if (currentLCM == 0) {
currentLCM = nums[j];
} else {
currentLCM = Lcm(currentLCM, nums[j]);
}
if (currentLCM == k) {
count++;
} else if (currentLCM > k) {
break; // 剪枝:LCM已经超过k,不需要继续扩展
}
}
}
return count;
}
}
var subarrayLCM = function(nums, k) {
function gcd(a, b) {
while (b !== 0) {
let temp = b;
b = a % b;
a = temp;
}
return a;
}
function lcm(a, b) {
return (a * b) / gcd(a, b);
}
let count = 0;
for (let i = 0; i < nums.length; i++) {
let currentLCM = nums[i];
for (let j = i; j < nums.length; j++) {
currentLCM = lcm(currentLCM, nums[j]);
if (currentLCM === k) {
count++;
} else if (currentLCM > k) {
break;
}
}
}
return count;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n² · log(max(nums))) | 双重循环 O(n²),每次计算 LCM 需要 O(log(max(nums))) |
| 空间复杂度 | O(1) | 只使用了常数级别的额外空间 |