zzcms网站开发wordpress 文章密码保护
好的!让我们详细解释 LeetCode 1524. 和为奇数的子数组数目 这道题的思路和解法。
题目: https://leetcode.cn/problems/number-of-sub-arrays-with-odd-sum/description/
题目分析
问题描述:
给定一个整数数组 arr,返回其中和为奇数的子数组的数目。由于答案可能很大,结果需对 10^9 + 7 取余。
示例:
输入:arr = [1, 3, 5]
输出:4
解释:
- 子数组
[1]、[3]、[5]的和均为奇数。 - 子数组
[1, 3, 5]的和为1 + 3 + 5 = 9,也是奇数。
暴力解法(超时)
思路:
枚举所有子数组,计算每个子数组的和并判断奇偶性。
代码:
class Solution {
public:int numOfSubarrays(vector<int>& arr) {const int MOD = 1e9 + 7;int count = 0;int n = arr.size();for (int i = 0; i < n; i++) {int sum = 0;for (int j = i; j < n; j++) {sum += arr[j];if (sum % 2 == 1) {count = (count + 1) % MOD;}}}return count;}
};
复杂度:
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
优化解法:前缀和 + 奇偶计数
核心思路:
- 前缀和的奇偶性:子数组
[i, j]的和为prefix[j+1] - prefix[i],其中prefix[k]表示前k个元素的和。 - 奇偶性规律:若
prefix[j+1]和prefix[i]的奇偶性不同,则子数组和为奇数。 - 哈希表统计:动态维护前缀和为奇数和偶数的次数,遍历数组时累加符合条件的子数组数目。
代码:
class Solution {
public:int numOfSubarrays(vector<int>& arr) {const int MOD = 1e9 + 7;int count = 0;int prefix = 0; // 当前前缀和int odd = 0; // 前缀和为奇数的次数int even = 1; // 前缀和为偶数的次数(初始包含前缀和为0的情况)for (int num : arr) {prefix += num;if (prefix % 2 == 0) {// 当前前缀和为偶数,加上之前前缀和为奇数的次数count = (count + odd) % MOD;even++;} else {// 当前前缀和为奇数,加上之前前缀和为偶数的次数count = (count + even) % MOD;odd++;}}return count;}
};
复杂度:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
详细解释
-
前缀和的奇偶性:
对于子数组[i, j],其和为prefix[j+1] - prefix[i]。- 若
prefix[j+1]为偶数,prefix[i]为奇数,则和为奇数。 - 若
prefix[j+1]为奇数,prefix[i]为偶数,则和为奇数。
- 若
-
动态维护奇偶次数:
odd:记录遍历到当前位置时,前缀和为奇数的次数。even:记录遍历到当前位置时,前缀和为偶数的次数。- 初始值:
even = 1,因为空数组的前缀和为 0(偶数)。
-
累加结果:
- 若当前前缀和为偶数,则它可以与之前所有奇数前缀和形成有效子数组,累加
odd。 - 若当前前缀和为奇数,则它可以与之前所有偶数前缀和形成有效子数组,累加
even。
- 若当前前缀和为偶数,则它可以与之前所有奇数前缀和形成有效子数组,累加
示例验证
输入:arr = [1, 3, 5]
步骤如下:
- 初始状态:
prefix = 0,odd = 0,even = 1,count = 0。 - 处理 1:
prefix = 1(奇数),count += even = 1,odd = 1,even = 1。 - 处理 3:
prefix = 4(偶数),count += odd = 1 + 1 = 2,odd = 1,even = 2。 - 处理 5:
prefix = 9(奇数),count += even = 2 + 2 = 4,odd = 2,even = 2。
最终结果:4,与预期一致。
总结
通过前缀和的奇偶性分析和动态计数,我们将时间复杂度从 O(n²) 优化到 O(n),空间复杂度为 O(1)。这种方法适用于所有类似的“子数组和满足某种奇偶性条件”的问题,核心在于利用前缀和的奇偶性快速查找配对。
