📌  相关文章
📜  总数不能被K整除的最长可能子数组的计数(1)

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

总数不能被K整除的最长可能子数组的计数

简介

在给定的数组中,找到最长可能的子数组,使得该子数组的数字总和不能被K整除。本题需要返回符合要求的子数组数量。

解题思路
方法一:前缀和

我们可以通过前缀和来快速计算子数组的和,具体步骤如下:

  1. 定义一个前缀和数组preSum,其中preSum[i]表示原数组的前i个元素的和。
  2. 对于任意长度为k的子数组[i, j],其和可以表示为preSum[j] - preSum[i-1]。我们可以通过计算该子数组的余数来判断其是否满足条件。
  3. 使用哈希表保存余数以及其最后出现的位置。
  4. 当我们遍历到第j个元素时,记当前前缀和为s,则当前子数组为[i+1, j],其和为s - preSum[i]。若(s - preSum[i]) % K的余数为r,则之前出现过余数为r的位置为k,则当前子数组也满足要求,其长度为j - k,可以加入答案中。
方法二:动态规划

首先考虑求解一个子问题:对于长度为k的子数组,如果它能被K整除,那么在该子数组前面加入任意长度的子数组后,它们的和一定也能被K整除。因此,我们可以使用一个状态dp[i][j],表示以第i个元素结尾的长度为j的子数组是否能被K整除。若第i个元素能被K整除,则dp[i][1] = true

然后我们考虑如何通过状态转移来求解答案:

  1. 当遍历到第i个元素时,如果该元素能被K整除,则相当于找到一个长度为1的子数组,该子数组的和不能被K整除。此时,最长满足条件的子数组长度为1。
  2. 如果第i个元素不能被K整除:
  • 对于该元素之前的所有具有状态dp[j][x] = true的子数组(其中j为小于i的非负整数,x为小于j的正整数),如果在这些子数组后面加入第i个元素,其和仍然不能被K整除,那么它们对答案的贡献为1。同时,我们可以更新状态dp[i][1]为true。
  • 对于该元素之前的所有具有状态dp[j][y] = true的子数组(其中j为小于i的非负整数,y为大于等于1且小于j的正整数),如果在这些子数组后面加入第i个元素,其和仍然不能被K整除,那么它们对答案的贡献为j - y + 1。此时,我们可以更新状态dp[i][j-y+2]为true。

用代码表示即为:

if (nums[i] % K == 0)
    ans = 1;
for (int j = i - 1; j >= 0; j--) {
    for (int len = 1; len <= i - j; len++) {
        if (nums[j+len-1] % K == 0) {
            ans = max(ans, len);
            break;
        }
        if ((preSum[j+len-1] - preSum[j] + nums[i]) % K != 0) {
            ans = max(ans, len+1);
        }
    }
}
复杂度分析
方法一:前缀和

时间复杂度:O(n) 空间复杂度:O(K)

方法二:动态规划

时间复杂度:O(n^2) 空间复杂度:O(n^2)

完整代码
方法一:前缀和
int subarraysDivByK(vector<int>& A, int K) {
    int ans = 0, sum = 0;
    unordered_map<int, int> cnt{{0, 1}};
    for (int i = 0; i < A.size(); i++) {
        sum = (sum + A[i]) % K;
        if (sum < 0) sum += K;
        ans += cnt[sum];
        cnt[sum]++;
    }
    return ans;
}
方法二:动态规划
int subarraysDivByK(vector<int>& nums, int K) {
    int n = nums.size(), ans = 0;
    vector<vector<bool>> dp(n, vector<bool>(n+1, false));
    for (int i = 0; i < n; i++) {
        if (nums[i] % K == 0) {
            ans = 1;
            dp[i][1] = true;
        }
        for (int j = i - 1; j >= 0; j--) {
            for (int len = 1; len <= i - j; len++) {
                if (nums[j+len-1] % K == 0) {
                    ans = max(ans, len);
                    break;
                }
                if (dp[j][len] && (preSum[j+len-1] - preSum[j] + nums[i]) % K != 0) {
                    ans = max(ans, len+1);
                    dp[i][len+1] = true;
                }
            }
        }
    }
    return ans;
}