Nameless Site

But one day, you will stand before its decrepit gate,without really knowing why.

0%

统计优美子数组

来自Leetcode1248题统计优美子数组

给你一个整数数组 nums 和一个整数 k

如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。

请返回这个数组中「优美子数组」的数目。

示例 1:

1
2
3
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。

滑动窗口

用一个辅助数组odd存放原数组nums里奇数位的下标,接着对odd数组遍历,取当前数值j(即原本nums数组里某奇数的下标为j)到其前一个奇数的距离(即odd[i] - odd[i-1])乘上后k个奇数的下标以及他能拓展的长度。

总的来说就是用数组存了2个指针,这2个指针刚好指向第一个奇数和第k个奇数,接下来再分别往前往后拓展,拓展相乘的结果就是当前k个连续奇数的可能情况,最后对所有结果再累加即可。image.png

图来自力扣题解

将奇数的下标用数组储存,其中橘色部分为奇数出现的位置,蓝色部分标定数组的边界。对于每种子情形,其可能数为:(arr[i] - arr[i - 1]) * (arr[i + k] - arr[i + k - 1]),其中,左边界为−1,右边界为arr.length。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int numberOfSubarrays(int[] nums, int k){
int len = nums.length;
int[] odd = new int[len+2];
int ans = 0 , cnt = 0;
for (int i = 0 ; i < len; ++i){
if((nums[i] & 1) == 1)
odd[++cnt] = i;
}
odd[0] = -1;
odd[++cnt] = len;
for(int i = 1 ; i + k <= cnt ; ++i)
ans += (odd[i] - odd[i-1]) * (odd[i+k] - odd[i+k-1]);
return ans;
}
}

前缀和

来自sweetiee

  1. 计算前缀和数组 arr:遍历原数组,每遍历一个元素,计算当前的前缀和(即到当前元素为止,数组中有多少个奇数);
  2. 对上述前缀和数组,双重循环统计 arr[j] - arr[i] == k 的个数,这样做是 O(N^2)O(N2) 的(这里会超时哦)。
  3. 优化:因此,我们可以像「1. 两数之和」那样使用 HashMap 优化到 O(N),键是「前缀和」,值是「前缀和的个数」(下面代码中具体使用的是 int[] prefixCnt 数组,下标是「前缀和」,值是「前缀和的个数」),因此我们可以遍历原数组,每遍历到一个元素,计算当前的前缀和 sum,就在 res 中累加上前缀和为 sum - k 的个数。
  4. 这里的前缀和指的是从0开始到当前元素这个区间的奇数的个数,所以两个前缀和的差就是两个元素之间的奇数的个数,我们要找的就是奇数个数为k的区间哈。prefixCnt数组是用来统计的是前缀和的个数,数组下标是前缀和,值是个数。如果当前的前缀数组的奇数个数为s2,那我们需要看有多少个前缀数组的奇数个数为 s1 = s2 - k 的,那么这些区间的奇数个数都是k,就累加到res中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
// 数组 prefixCnt 的下标是前缀和(即当前奇数的个数),值是前缀和的个数。
int[] prefixCnt = new int[nums.length + 1];
prefixCnt[0] = 1;
// 遍历原数组,计算当前的前缀和,统计到 prefixCnt 数组中,
// 并且在 res 中累加上与当前前缀和差值为 k 的前缀和的个数。
int res = 0, sum = 0;
for (int num: nums) {
sum += num & 1;
prefixCnt[sum]++;
if (sum >= k) { //说明已经有k个奇数
res += prefixCnt[sum - k];
}
}
return res;
}
}