Medium
题目描述
给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数组中没有奇数,所以不存在优美子数组。
示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
提示:
1 <= nums.length <= 500001 <= nums[i] <= 10^51 <= k <= nums.length
解题思路
解题思路
这道题要求统计包含恰好 k 个奇数的连续子数组个数。我们可以从多个角度来解决:
方法一:前缀和 + 哈希表(推荐)
将每个奇数看作 1,偶数看作 0,问题转化为求和为 k 的子数组个数。使用前缀和思想,对于位置 i,我们需要找到有多少个位置 j (j < i) 使得 prefixSum[i] - prefixSum[j] = k,即 prefixSum[j] = prefixSum[i] - k。
用哈希表记录每个前缀和出现的次数,遍历数组时:
- 更新当前前缀和
- 查找
prefixSum - k在哈希表中的出现次数,累加到结果 - 更新哈希表中当前前缀和的计数
方法二:滑动窗口
使用两个指针维护一个包含恰好 k 个奇数的窗口。关键在于当窗口内奇数个数等于 k 时,需要计算以当前右端点结尾的所有有效子数组个数。
方法三:奇数位置记录
记录所有奇数的位置,然后对于每 k 个连续的奇数,计算包含它们的子数组个数。
前缀和方法实现简洁,时间复杂度最优,是最推荐的解法。
代码实现
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
unordered_map<int, int> prefixCount;
prefixCount[0] = 1; // 初始化,前缀和为0出现1次
int prefixSum = 0;
int result = 0;
for (int num : nums) {
// 奇数计为1,偶数计为0
prefixSum += num % 2;
// 查找prefixSum - k是否存在
if (prefixCount.find(prefixSum - k) != prefixCount.end()) {
result += prefixCount[prefixSum - k];
}
// 更新当前前缀和的计数
prefixCount[prefixSum]++;
}
return result;
}
};
class Solution:
def numberOfSubarrays(self, nums: List[int], k: int) -> int:
prefix_count = {0: 1} # 初始化,前缀和为0出现1次
prefix_sum = 0
result = 0
for num in nums:
# 奇数计为1,偶数计为0
prefix_sum += num % 2
# 查找prefix_sum - k是否存在
if prefix_sum - k in prefix_count:
result += prefix_count[prefix_sum - k]
# 更新当前前缀和的计数
prefix_count[prefix_sum] = prefix_count.get(prefix_sum, 0) + 1
return result
public class Solution {
public int NumberOfSubarrays(int[] nums, int k) {
Dictionary<int, int> prefixCount = new Dictionary<int, int>();
prefixCount[0] = 1; // 初始化,前缀和为0出现1次
int prefixSum = 0;
int result = 0;
foreach (int num in nums) {
// 奇数计为1,偶数计为0
prefixSum += num % 2;
// 查找prefixSum - k是否存在
if (prefixCount.ContainsKey(prefixSum - k)) {
result += prefixCount[prefixSum - k];
}
// 更新当前前缀和的计数
if (prefixCount.ContainsKey(prefixSum)) {
prefixCount[prefixSum]++;
} else {
prefixCount[prefixSum] = 1;
}
}
return result;
}
}
var numberOfSubarrays = function(nums, k) {
const prefixCount = new Map();
prefixCount.set(0, 1); // 初始化,前缀和为0出现1次
let prefixSum = 0;
let result = 0;
for (const num of nums) {
// 奇数计为1,偶数计为0
prefixSum += num % 2;
// 查找prefixSum - k是否存在
if (prefixCount.has(prefixSum - k)) {
result += prefixCount.get(prefixSum - k);
}
// 更新当前前缀和的计数
prefixCount.set(prefixSum, (prefixCount.get(prefixSum) || 0) + 1);
}
return result;
};
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 前缀和 + 哈希表 | O(n) | O(n) |
| 滑动窗口 | O(n) | O(1) |
| 奇数位置记录 | O(n) | O(k) |
推荐使用前缀和方法,代码简洁且容易理解。