题目描述

✅ 1524. 和为奇数的子数组数目

image-20260929084854512

image-20260929084854647

题意分析

统计有多少个非空连续子数组的元素和为奇数,结果对 $10^9+7$ 取模。不同起止位置分别计数,即使内容相同也属于不同子数组。

只关心总和的奇偶,不要求子数组只包含奇数。多个奇数相加可能变成偶数,偶数元素也可以出现在合法子数组中。

解法:前缀和奇偶计数

核心思路

[!blue]

子数组和等于右侧前缀和减去左端之前的前缀和。两个数的差为奇数,当且仅当它们奇偶不同,因此不必保存完整前缀和,只需要记录奇偶。

处理当前元素后,若当前前缀为奇数,那么每一个历史偶前缀都可以作为左端之前的位置,产生一个以当前元素结尾的奇数和子数组,新增数量就是 evenCount。当前前缀为偶数时,同理新增 oddCount。

统计贡献后,将当前前缀登记到对应类别。每个历史位置都代表不同起点,所以保存出现次数而非是否存在;每个子数组只在其右端被处理时计入,避免重复。

初始空前缀的和为零,是一个偶前缀,因此 evenCount = 1、oddCount = 0。这使从数组首项开始的子数组也能统一写成两个前缀之差。

当前奇偶通过 (sum + num) % 2 更新,sum 始终只保存零或一。每轮对累计答案取模,不影响以后按历史前缀频次继续计数。

解题步骤

  1. 登记空前缀为偶数一次,当前奇偶与答案为零。
  2. 扫描新元素并更新当前前缀奇偶。
  3. 将历史相反奇偶的前缀数量加入答案。
  4. 增加当前这一类前缀的数量,并对答案取模。
  5. 返回最终计数。

代码实现

class Solution {
    public int numOfSubarrays(int[] arr) {
        int mod = 1000000007;
        // 空前缀的和为偶数,用于统计从数组开头开始的子数组。
        int evenCount = 1;
        int oddCount = 0;
        int sum = 0;
        long answer = 0;

        for (int num : arr) {
            sum = (sum + num) % 2;

            if (sum == 1) {
                // 当前前缀为奇数,与此前每个偶前缀组成奇数和区间。
                answer += evenCount;
                oddCount++;
            } else {
                // 当前前缀为偶数,累加此前奇前缀的贡献。
                answer += oddCount;
                evenCount++;
            }

            answer %= mod;
        }

        return (int) answer;
    }
}
func numOfSubarrays(arr []int) int {
    const mod = 1000000007
    // 空前缀的和为偶数,用于统计从数组开头开始的子数组。
    evenCount := 1
    oddCount := 0
    sum := 0
    answer := 0

    for _, num := range arr {
        sum = (sum + num) % 2
        if sum == 1 {
            // 当前前缀为奇数,与此前每个偶前缀组成奇数和区间。
            answer = (answer + evenCount) % mod
            oddCount++
        } else {
            // 当前前缀为偶数,累加此前奇前缀的贡献。
            answer = (answer + oddCount) % mod
            evenCount++
        }
    }

    return answer
}

复杂度分析

  • 时间复杂度:$O(n)$,每个元素只更新一次奇偶、计数和答案。
  • 空间复杂度:$O(1)$,只保存两类频次与固定数量的变量。

关键点总结

[!green]

  • 前缀奇偶不同,区间差才是奇数。
  • 历史频次同时表示所有可用左端,避免逐个枚举。
  • 空前缀参与计数,统一覆盖从首项开始的区间。
  • 完整前缀数值可丢弃,只保留奇偶仍足以判断。

易错点总结

[!yellow]

  • 偶前缀初始设为零,会漏掉从数组开头开始的合法子数组。
  • 累加同类前缀频次,会统计成偶数和区间。
  • 只数奇数元素会漏掉长度大于一的合法区间,也不能判断多个奇数相加的结果。
  • 本题要求模后答案,累计时不能忘记取模。

相似题目

题目 难度 关联与区别
560. 和为 K 的子数组 中等 同样按前缀分类,本题只关心奇偶,当前前缀与相反奇偶的历史前缀配对。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2021/10007082
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!