📅  最后修改于: 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