📌  相关文章
📜  要删除的最小子数组的长度,以使其余元素的总和可被K整除(1)

📅  最后修改于: 2023-12-03 14:57:21.366000             🧑  作者: Mango

题目描述

给定一个非负整数数组 A,和一个整数 K,需要找到要删除的最小子数组的长度,以使其余元素的总和可被 K 整除。

示例

输入:

A = [5, 6, 7, 8, 9]
K = 6

输出:

3

解释: 删除 [5,6] 或者 [7,8] 或者 [8,9] 都可以使剩余元素的和能够被6整除。

解题思路

首先,将数组 A 中所有元素求和得到 total,则可知若 total % K == 0,则不需要删除任何数字,直接返回 0 即可;否则,需要将一些元素删除,以使得剩余元素的和能被 K 整除。

现在问题转化为,如何求出要删除的最小子数组的长度。

想要使剩余元素的和能被 K 整除,就需要将总和中一些不合法的子集(即不能被 K 整除的子集)删除。假设有两个合法的子数组:

$$ sum[i] = \sum_{k=0}^{i} A[k] \qquad sum[j] = \sum_{k=0}^{j} A[k] $$

如果这两个子数组的差可以被 K 整除,则说明从 sum[i]sum[j](不包括 sum[j]) 之间的所有元素的和都可以被 K 整除。即:

$$ (sum[j] - sum[i]) \bmod K = 0 $$

$$ sum[j] \bmod K = sum[i] \bmod K $$

因此,我们只需要保存每个前缀和的模 K 的余数最早出现的位置即可,这样每次新出现的位置减去最早出现的位置,就能得到一个新的合法子数组,而且在所有的合法子数组中,我们要删除的数组长度一定是最小的。

时间复杂度为 $O(N)$。

代码实现
def minSubarray(nums: List[int], k: int) -> int:
    # 数组前缀和
    prefix_sum = [0] * (len(nums) + 1)
    for i in range(len(nums)):
        prefix_sum[i + 1] = prefix_sum[i] + nums[i]
    # 模 K 的余数 -> 前缀和数组下标
    mod_dict = {0: 0}
    ans = float('inf')
    for i, prefix_sum_i in enumerate(prefix_sum):
        mod_i = prefix_sum_i % k
        mod_dict.setdefault(mod_i, i)
        # 如果 mod_dict 中已有 mod_i,说明当前状态为 s1,之前出现的状态为 s2,则 s1-s2 即为一个合法子数组
        mod_j = (mod_i - k) % k
        j = mod_dict.get(mod_j)
        if j is not None:
            ans = min(ans, i - j)
    return ans if ans < len(nums) else -1