题目
https://leetcode-cn.com/problems/count-number-of-nice-subarrays/
滑动窗口
使用双指针维护一个逻辑意义上的窗口区间为[left, right)。随着右指针的移动统计窗口内的奇数个数,如果奇数个数为k了,这时候再统计与当前窗口相关的子串的个数,可以这样统计:
- 计算算窗口内最右侧奇数右边有几个偶数,用rightEvenCnt表示,也就是说有几种选择结尾的方式,由于可以什么偶数都不选所以一共有 rightEvenCnt + 1 种结尾方式
- 同理,计算当前窗口最左侧奇数左边的偶数个数 leftEvenCnt,一共有 leftEvenCnt + 1种选择方式
- 这样与当前窗口相关的子串有 (rightEvenCnt + 1)* (leftEvenCnt + 1)
最后将所有符合条件的滑动窗口得到的结果累加即可。
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int count = 0, len = nums.length;
int left = 0, right = 0, res = 0;
while (right < len) {
int num = nums[right];
if (num % 2 == 1) count++;
right++;
if (count == k) {
int temp = right;
while (right < len && nums[right] % 2 == 0) {
right++;
}
int rightEvenCnt = right - temp;
int leftEvenCnt = 0;
while (nums[left] % 2 == 0) {
leftEvenCnt++;
left++;
}
res += (rightEvenCnt + 1) * (leftEvenCnt + 1);
left++;
count--;
}
}
return res;
}
}
前缀和
前缀和可以快速计算数组某个区间的和,这题也可以使用前缀和来快速计算某个区间的奇数个数只需要对求前缀和的方法稍加改变:
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + (nums[i - 1] & 1);
cnt[sum[i]]++;
}
有了前缀和就可以使用两层for循环遍历所有区间
[
i
,
j
]
[i, j]
[i,j],将
s
u
m
[
j
+
1
]
−
s
u
m
[
i
]
=
k
sum[ j + 1 ] - sum[i] = k
sum[j+1]−sum[i]=k 的区间个数统计出来。但这样时间复杂度为
O
(
n
2
)
O(n^2)
O(n2), 需要进行优化。
我们可以直接知道
s
u
m
[
j
+
1
]
sum[j + 1]
sum[j+1]的值,也知道了需要寻找的区间和为 k, 那么只需要统计 区间和为
s
u
m
[
j
+
1
]
−
k
sum[j + 1] - k
sum[j+1]−k的区间数量即可。
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int n = nums.length;
int[] sum = new int[n + 1];
int[] cnt = new int[n + 1];
cnt[0] = 1;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + (nums[i - 1] & 1);
cnt[sum[i]]++;
}
// 考虑 [i, j] 范围内 奇数 个数为 k的区间有几个
int res = 0;
for (int j = 1; j <= n; j++) {
if (sum[j] >= k)
res += cnt[sum[j] - k];
}
return res;
}
}
可以对空间复杂度进行优化,使用一个变量计算前缀和
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int n = nums.length;
int sum = 0;
int[] cnt = new int[n + 1];
cnt[0] = 1;
int res = 0;
for (int num : nums) {
sum += num & 1;
cnt[sum]++;
if (sum >= k) {
res += cnt[sum - k];
}
}
return res;
}
}