314. 二进制子数组问题 | 豆包MarsCode AI刷题

53 阅读3分钟

题解

这道题目要求我们从一个正整数数组中选取一个最短的连续子数组,使得该子数组的乘积在二进制表示中尾部至少有 k0。换句话说,子数组的乘积必须是 2^k 的倍数。

要解决这个问题,我们需要关注数组中每个元素的因子 2 的个数,因为它决定了该元素对最终乘积尾部 0 的贡献。具体来说,如果一个数能够被 2 整除,那么它的二进制表示末尾就会有 0。因此,对于每个元素,我们要计算它包含多少个因子 2,然后累加这些因子来判断当前子数组的乘积是否有足够多的尾随 0

解题思路

  1. 因子 2 的计算:每个数可以通过不断地除以 2 来计算它包含多少个因子 2。例如,数 8 的二进制表示为 1000,它有 3 个尾随的 0,即有 3 个因子 2
  2. 滑动窗口:我们可以使用滑动窗口(两个指针 lr)来遍历整个数组。r 指针用于扩展窗口,而 l 指针则用于缩小窗口,确保在子数组的乘积满足条件时,找出最短的子数组。
  3. 条件判断:每次扩展 r,我们累加窗口内每个数的因子 2 的个数,当当前窗口内因子 2 的个数大于或等于 k 时,说明子数组满足条件。此时,我们记录当前子数组的长度,并尝试通过移动 l 来缩小窗口,找到更短的满足条件的子数组。
  4. 输出结果:如果找到了满足条件的子数组,返回最短的长度,否则返回 -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;
}

代码详细解释

  1. 计算因子 2 的个数:对于每个 a[r],我们用一个 while 循环去除 2,直到它无法再被 2 整除。在此过程中,我们统计它被 2 除的次数,即 countOfTwos,表示该数对乘积尾部 0 的贡献。
  2. 滑动窗口:我们使用两个指针 lrr 扩展窗口,l 缩小窗口。每当窗口内因子 2 的个数大于或等于 k 时,我们就计算当前子数组的长度,并试图通过移动 l 来缩小窗口,找到最短的子数组。
  3. 更新最短长度:当窗口满足条件时,res = Math.min(res, r - l + 1) 用于更新最短的子数组长度。
  4. 返回结果:最终,如果找到了符合条件的子数组,则返回最短的子数组长度;否则返回 -1

复杂度分析

  • 时间复杂度:每个元素只会被 r 指针和 l 指针分别访问一次,因此时间复杂度为 O(n),其中 n 是数组的长度。
  • 空间复杂度:我们使用了一个 HashMap 来记录每个位置的因子 2 的个数,空间复杂度为 O(n)。