Leetcode_1524_和为奇数的子数组数目_前缀和_动态规划

听到研三学长面试时候的一道真题,顺手搜了一波题,就找到一个类似的,顺手写一下,真题写在后面了。
先贴一个n2的超时做法:

class Solution {
    public int numOfSubarrays(int[] arr) {
        int len = arr.length;
        int ans = 0;
        int mod = 1000000000 + 7;
        // dp[i][j]代表从第i个开始,第j个结束的子数组的奇偶性,false为偶,true为奇
        boolean[][] dp = new boolean[len][len];
        dp[0][0] = arr[0] % 2 == 0 ? false : true;
        ans += dp[0][0] == true ? 1 : 0;
        for (int i = 1; i < len; i++) {
            dp[0][i] = arr[i] % 2 == 0 ? dp[0][i - 1] : !dp[0][i - 1];
            ans += dp[0][i] == true ? 1 : 0;
        }
        for (int i = 1; i < len; i++) {
            dp[i][i] = arr[i - 1] % 2 == 0 ? dp[i - 1][i] : !dp[i - 1][i];
            ans += dp[i][i] == true ? 1 : 0;
            ans %= mod;
            for (int j = i + 1; j < len; j++) {
                dp[i][j] = arr[j] % 2 == 0 ? dp[i][j - 1] : !dp[i][j - 1];
                ans += dp[i][j] == true ? 1 : 0;
                ans %= mod;
            }
        }
        return ans;
    }
}

优化一下:

dp[i]代表以第i个数结尾的子数组总个数
我们知道只有奇数才有改变奇偶性的能力,具体的数值其实无所谓
我们可以将这个数组抽象为 a1个奇数,b1个偶数,a2个奇数,b2个偶数这样的形式
当我们在当前数组末尾添加一个数的时候
1. 如果arr[i]是奇数,前面必须要有偶数个奇数,头部的偶数可加可不加
我们将arr[i]当作目前最后一个奇数
dp[i+1] = dp[i] + (倒数第一个奇数前的连续偶数个数 + 1) + (倒数第三个奇数前的连续偶数个数 + 1) + …
(arr[i]前的连续偶数个数 + 1)代表我们不要之前数组中的奇数,只要arr[i]前的连续偶数,因为还可以不取,所以+1;后面的累加同理
2. 如果arr[i]是偶数,前面必须要有奇数个奇数,头部的偶数可加可不加
dp[i+1] = dp[i] + (倒数第一个奇数前的连续偶数个数 + 1) + (倒数第三个奇数前的连续偶数个数 + 1) + …
我们可以发现无论arr[i]的奇偶性如何,状态转移方程不变
我们现在还缺少的,无非就是每个相邻的奇数与奇数之间的偶数的数量,预处理算一下,ac


当然正解还是经典前缀和

主要思路:如果新加的一个数是偶数,那么我们需要去掉任意一个前缀和为奇数的前缀,也就是ans+=ji;同理,如果新加的一个数是奇数,那么我们需要去掉任意一个前缀和为偶数的前缀,也就是ans+=ou。我们只需要在遍历的时候,维护一下ji和ou就可以了。

class Solution {
    public int numOfSubarrays(int[] arr) {
        int len = arr.length;
        int mod = 1000000000 + 7;
        int ans = 0;
        // 前缀和为奇数或偶数的数量
        int ou = 1;
        int ji = 0;
        //false 为偶数,true为奇数
        boolean sum = false;
        for (int i = 0; i < len; i++) {
            sum = arr[i] % 2 == 0 ? sum : !sum;
            if (sum == false) {
                ans += ji;
                ou++;
            } else {
                ans += ou;
                ji++;
            }
            ans %= mod;
        }
        return ans;
    }
}

面试真题:将子数组改为子序列

因为是子序列了,所以可以忽略数的值,只要它们的奇偶性。

因为结果为奇数,所以只能是偶数个奇数,偶数多少个无所谓。

我们先遍历获得奇数和偶数的个数ji和ou
然后得到答案:
a n s = ∑ i = 0 o u C o u i ∗ ∑ i = 1 [ i + = 2 ] j i C j i i ans=\sum_{i=0}^{ou}C_{ou}^{i}*\sum_{i=1[i+=2]}^{ji}C_{ji}^{i} ans=i=0ouCouii=1[i+=2]jiCjii

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值