题解
这道题目要求我们从一个正整数数组中选取一个最短的连续子数组,使得该子数组的乘积在二进制表示中尾部至少有 k 个 0。换句话说,子数组的乘积必须是 2^k 的倍数。
要解决这个问题,我们需要关注数组中每个元素的因子 2 的个数,因为它决定了该元素对最终乘积尾部 0 的贡献。具体来说,如果一个数能够被 2 整除,那么它的二进制表示末尾就会有 0。因此,对于每个元素,我们要计算它包含多少个因子 2,然后累加这些因子来判断当前子数组的乘积是否有足够多的尾随 0。
解题思路
- 因子 2 的计算:每个数可以通过不断地除以
2来计算它包含多少个因子2。例如,数8的二进制表示为1000,它有 3 个尾随的0,即有 3 个因子2。 - 滑动窗口:我们可以使用滑动窗口(两个指针
l和r)来遍历整个数组。r指针用于扩展窗口,而l指针则用于缩小窗口,确保在子数组的乘积满足条件时,找出最短的子数组。 - 条件判断:每次扩展
r,我们累加窗口内每个数的因子2的个数,当当前窗口内因子2的个数大于或等于k时,说明子数组满足条件。此时,我们记录当前子数组的长度,并尝试通过移动l来缩小窗口,找到更短的满足条件的子数组。 - 输出结果:如果找到了满足条件的子数组,返回最短的长度,否则返回
-1。
代码解析
public static int solution(int n, int k, int[] a) {
// 用于存储每个位置的2的因子个数
Map<Integer, Integer> occ = new HashMap<>();
// 初始化滑动窗口的左右指针以及2的因子总数
int l = 0, r = 0, cnt = 0, res = Integer.MAX_VALUE;
// 滑动窗口扩展右指针
while (r < n) {
int tmp = a[r];
int countOfTwos = 0;
// 计算当前数a[r]的因子2的个数
while (tmp % 2 == 0) {
tmp /= 2;
countOfTwos++;
}
// 更新当前窗口内因子2的总数
cnt += countOfTwos;
// 记录位置r对应的因子2的个数
occ.put(r, countOfTwos);
// 确保窗口内因子2的个数满足要求,尝试缩小窗口
while (cnt >= k && l <= r) {
res = Math.min(res, r - l + 1); // 记录最短子数组长度
// 缩小窗口,更新cnt
cnt -= occ.get(l);
l++;
}
// 扩展右指针
r++;
}
// 如果没有找到符合条件的子数组,返回-1
return res == Integer.MAX_VALUE ? -1 : res;
}
代码详细解释
- 计算因子 2 的个数:对于每个
a[r],我们用一个while循环去除2,直到它无法再被2整除。在此过程中,我们统计它被2除的次数,即countOfTwos,表示该数对乘积尾部0的贡献。 - 滑动窗口:我们使用两个指针
l和r,r扩展窗口,l缩小窗口。每当窗口内因子2的个数大于或等于k时,我们就计算当前子数组的长度,并试图通过移动l来缩小窗口,找到最短的子数组。 - 更新最短长度:当窗口满足条件时,
res = Math.min(res, r - l + 1)用于更新最短的子数组长度。 - 返回结果:最终,如果找到了符合条件的子数组,则返回最短的子数组长度;否则返回
-1。
复杂度分析
- 时间复杂度:每个元素只会被
r指针和l指针分别访问一次,因此时间复杂度为 O(n),其中n是数组的长度。 - 空间复杂度:我们使用了一个
HashMap来记录每个位置的因子2的个数,空间复杂度为 O(n)。