📌  相关文章
📜  乘积可被 k 整除的最小子数组(1)

📅  最后修改于: 2023-12-03 15:21:38.730000             🧑  作者: Mango

乘积可被 k 整除的最小子数组

当我们需要在数组中找到一个连续的子数组,使得它的乘积可以被 k 整除时,可以使用前缀积和余数的思想来解决。

前缀积

前缀积是指从数组的第一个元素到当前元素的所有元素的乘积。我们可以用一个数组 prefixProduct 来保存所有前缀积:

int[] prefixProduct = new int[nums.length];
prefixProduct[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
    prefixProduct[i] = prefixProduct[i-1] * nums[i];
}
余数

当一个数被另一个数整除时,它们的余数相同。因此,我们可以用余数来判断一个乘积是否可以被 k 整除。

假设前缀积 prefixProduct[i] 除以 k 的余数为 rem,则子数组 nums[j:i](其中 j < i)的乘积除以 k 的余数为 (prefixProduct[i]/prefixProduct[j-1])%k

我们可以用一个数组 remainders 来保存前缀积除以 k 的余数:

int[] remainders = new int[nums.length];
remainders[0] = nums[0] % k;
for (int i = 1; i < nums.length; i++) {
    remainders[i] = (remainders[i-1] * nums[i]) % k;
}
解题思路

首先,如果在数组中存在一个元素能够被 k 整除,即 nums[i]%k==0,则直接返回长度为 1 的子数组。

否则,我们可以枚举子数组的右端点 i,并计算该子数组的前缀积除以 k 的余数。如果余数为 0,则该子数组的乘积可以被 k 整除,返回该子数组。

否则,我们需要在前面的子数组中找到一个左端点 j,使得从 j+1 到 i 的子数组的余数为 remainders[i]/remainders[j] == 0,则该子数组的乘积可以被 k 整除,返回该子数组。

为了尽量减小子数组长度,我们需要在枚举右端点 i 的过程中,每次查找前缀积除以 k 的余数是否出现过。如果出现过,说明之前存在一个左端点 j,满足从 j+1 到 i 的子数组的余数为 0,此时可以计算该子数组的长度,并更新最小长度。

代码如下:

class Solution {
    public int minSubarray(int[] nums, int k) {
        if (nums == null || nums.length == 0) return -1;
        int n = nums.length, minLen = n+1;

        int[] prefixProduct = new int[n]; // 前缀积
        prefixProduct[0] = nums[0];
        for (int i = 1; i < n; i++) {
            prefixProduct[i] = prefixProduct[i-1] * nums[i];
        }

        int[] remainders = new int[n]; // 前缀积余数
        remainders[0] = nums[0] % k;
        for (int i = 1; i < n; i++) {
            remainders[i] = (remainders[i-1] * nums[i]) % k;
        }

        Map<Integer, Integer> map = new HashMap<>(); // key: 前缀积余数, value: 下标
        map.put(0, -1); // 当前元素单独成为可被整除的子数组,左端点为-1,右端点为0

        for (int i = 0; i < n; i++) {
            int rem = remainders[i];
            if (rem == 0) { // 该子数组的乘积可以被 k 整除
                minLen = Math.min(minLen, i+1);
            } else {
                int preIndex = map.getOrDefault(rem, -2); // 上一个余数为 rem 的下标
                if (preIndex != -2) { // 之前出现过余数为 rem 的前缀积
                    minLen = Math.min(minLen, i-preIndex);
                }
            }
            map.put(remainders[i], i);
        }

        return minLen <= n ? minLen : -1;
    }
}

时间复杂度为 O(n),空间复杂度为 O(n)。