1524 和为奇数的子数组数目(动态规划、前缀和)

1. 问题描述:

给你一个整数数组 arr 。请你返回和为奇数的子数组数目。由于答案可能会很大,请你将结果对 10 ^ 9 + 7 取余后返回。

示例 1:

输入:arr = [1,3,5]
输出:4
解释:所有的子数组为 [[1],[1,3],[1,3,5],[3],[3,5],[5]] 。
所有子数组的和为 [1,4,9,3,8,5].
奇数和包括 [1,9,3,5] ,所以答案为 4 。

示例 2 :

输入:arr = [2,4,6]
输出:0
解释:所有子数组为 [[2],[2,4],[2,4,6],[4],[4,6],[6]] 。
所有子数组和为 [2,6,12,4,10,6] 。
所有子数组和都是偶数,所以答案为 0 

示例 3:

输入:arr = [1,2,3,4,5,6,7]
输出:16

示例 4:

输入:arr = [100,100,99,99]
输出:4

示例 5:

输入:arr = [7]
输出:1

提示:

  • 1 <= arr.length <= 10^5
  • 1 <= arr[i] <= 100

2. 思路分析:

① 由题可知我们需要求解的是关于区间和的问题,看到区间和的字眼,首先需要想到的是前缀和的思路,所以比较容易想到的是遍历数组计算[0, i]的前缀和,依次检查长度为1,2....len(arr)的区间和然后判断是否是奇数对满足条件的区间进行计数即可,但是数组的长度在1 <= arr.length <= 10 ^ 5范围,所以遍历一遍数组 + 检查所有长度范围的区间和是否是奇数的思路肯定会超时,感觉还有其他更优的办法

② 于是看了一下力扣的官方题解,发现题解思路真的好棒,感觉有点像动态规划的思想,思路的核心:判断以当前位置i结束的子数组的前缀和是奇数还是偶数,并且需要使用两个变量来记录[0, i - 1]这个区间的前缀和为奇数、偶数的数目,其实我们并不关心前缀和为奇数还是偶数的数值,我们只关心前缀和为奇数还是偶数的数目,这样可以根据[0:i]这个位置的前缀和是奇数还是偶数 + [0: i - 1]前缀和为奇数与偶数的数目计算出[0:i]这个位置的和为奇数的区间数目,下面画出具体的区间会更好理解一点:

|______________|

0                           i

even, odd 记录[0, i - 1]位置的前缀和为偶数、奇数的数目

1)当sum[0:i]的前缀和为奇数的时候,这个时候我们需要看[0, i - 1]偶数even的数目,而这些前缀和为偶数的下一个位置到i这个位置的和肯定是奇数的

|______________|

0                           i

|__|____________|

0   j                        i

sum[0:i]为奇数,sum[0, j]为偶数, 所以sum[j + 1, i]为奇数,所以这里涉及到的是一个区间相减的问题,[0:i]和为奇数的区间减去[0:j]和为偶数的区间那么得到的[j + 1:i]这个区间和就为奇数:奇数和区间 + 奇数和区间 = 偶数和区间

所以我们只需要统计奇数与偶数的数目而并不需要统计具体的数值,有多少个偶数那么说明这些偶数的下一个位置到当前的结束位置i就有多少个和为奇数的区间

2)对于sum[0:i]为偶数的时候那么我们需要看的是[0:i - 1]有多少个的奇数,方法与上面的是一样的:奇数和区间 + 奇数和区间 = 偶数和区间

③ 感觉动态规划关于区间的问题很多是以位置i结尾的数组的某个含义

3. 代码如下:

力扣官方代码:

from typing import List


class Solution:
    def numOfSubarrays(self, arr: List[int]) -> int:
        MODULO = 10 ** 9 + 7
        odd, even = 0, 1
        subarrays = 0
        total = 0
        for x in arr:
            total += x
            subarrays += (odd if total % 2 == 0 else even)
            if total % 2 == 0:
                even += 1
            else:
                odd += 1
        return subarrays % MODULO

你可能感兴趣的:(动态规划,前缀和,力扣)