听到研三学长面试时候的一道真题,顺手搜了一波题,就找到一个类似的,顺手写一下,真题写在后面了。
先贴一个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=0ouCoui∗∑i=1[i+=2]jiCjii