题目信息:
滑动窗口解法:
/**
* 滑动窗口解法
* @param arr
* @param k
* @return
*/
public static int getNumberBySlideWindow(int[] arr, int k) {
int right = 0; // 窗口右指针
int left = 0; // 窗口左指针
int oddNum = 0; // 窗口中当前奇数的个数
int result = 0; // 最优子数组的数量
// 滑动窗口的右边界不能超过数组的边界
while (right < arr.length) {
// 从左往右移动窗口右指针,让窗口变大(或者向右滑动), 并统计窗口中奇数的个数
if (arr[right++] % 2 != 0) {
oddNum++;
}
// 需要实时判断当前的滑动窗口中是否已经存在了足够的奇数
if (oddNum == k) {
// 如果当前滑动窗口中已经有足够的奇数了,则可以计算当前滑动窗口中可能存在的最优子数列
// 首先,当前right指向的位置肯定是一个奇数,但奇数后面如果还有偶数,则也是可以加入到最优子数列中的
// 所以,我们要找到下一个奇数的位置,计算出两个奇数之间有多少个偶数
int temp = right;
while (right < arr.length && arr[right++] % 2 == 0);
// 窗口右边的偶数的个数
int rightEvenCount = right - temp;
// 同样,我们需要计算窗口右边的偶数个数
int leftEvenCount = 0;
while(arr[left++] % 2 == 0) leftEvenCount++; // left指针遇到第一个奇数后停止
// 那么这个滑动窗口中存在最优子数组的个数为:
result += ((leftEvenCount + 1) * (rightEvenCount + 1));
// 计算完成后,left要往前移动一步,但它此时指向的是一个奇数,会导致滑动窗口中的奇数少一个
left++;
oddNum--;
}
}
return result;
}
前缀和解法:
/**
* 前缀和的解法
* @param arr
* @param k
* @return
*/
public static int getNumberOfSubarrays(int[] arr, int k) {
// 最优子数列的数量
int result = 0;
// 奇数个数的前缀和
int sum = 0;
// 对应前缀和的个数, 例子:
// 数组 1 3 2 3 1,
// 取数组的前x项, 比如x=1时,只有一个奇数,则 sum = 1,而且只有一个奇数的也只有1种情况;
// 比如x=2时, 有两个奇数,则 sum = 2, 且也只有一种情况。
// 比如x = 3 时,依然有两个奇数, 则 sum = 2, 这时情况有两种了。
// ....
int[] map = new int[arr.length+1];
// 没有奇数的情况有一种,空串的情况下,是没有奇数的。
map[0] = 1;
// 遍历数组
for (int i = 0; i < arr.length; i++) {
// 遇到奇数,则前缀项中奇数的个数+1;
if (arr[i] % 2 != 0) sum++;
// 对应的sum的情况也会加1;
map[sum]++;
//这里非常关键,如果当前的sum,即前缀项中的奇数个数已经超过k了,说明前缀项中肯定能找到最优子
if (sum - k >= 0) {
// 那么最优子数列的数量有多少个呢?
// 可以拿 2 4 1 3 , k = 1 来举例, x表示遍历的索引:
// 遍历到 x = 0时,map[0] = 2 (因为初始条件map[0] = 1 ,而 arr[x] = 2不是奇数,所以map[0]++ = 2;
// 遍历到 x = 1时, map[0] = 3 (同样时因为 arr[x] = 4,不是奇数,map[0]++ = 3;
// 遍历到 x = 2时, map[1] = 1 (因为 arr[x] = 1 ,是奇数, map[1]++ = 1;
// 上面的条件判断成立:sum - k = 1 - 1 = 0 >=0;
// 这里为何是取map[sum-k]呢?可以这样理解:sum 代表 当前 奇数的个数, k代表最优子数组需要的奇数的个数, sum - k 就代表不需要的奇数的个数。
// 那么像一个滑动窗口一样,k是固定的,就是要看sum-k个奇数的时候有多少种情况。
result += map[sum - k];
}
}
return result;
}
Q.E.D.