[LeetCode]1248.优美子数组
给你一个整数数组 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 <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length
- 考虑暴力枚举法解题
考虑nums所有的子数组,检查每个子数组中奇数元素的个数,统计优美子数组的个数。因为双重循环求子数组,所以时间复杂度为O(n^2),再加上后面要遍历子数组求其中的奇数个数,时间复杂度为O(n),所有总的时间复杂度为O(n ^3),不符合题意。
题目中要求时间复杂度为O(n)
这里要引入一个概念,如何判断题目的时间复杂度呢?
最简单、快捷的方式就是通过观察题目中的数据范围来确定。
数据范围与算法大致时间复杂度的对应表如下:
本题的数据范围到达了 50000,因此我们将时间复杂度划定在 O(n)的范围内。
根据官方的题解我们可以用数学方法考虑这道题
2.数学方法
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int n = nums.length;
int[] odd = new int[n + 2];
//建立一个odd 数组来记录第 i 个奇数的下标
//此处数组开头和结尾预设了数组,做边界处理
int ans = 0, cnt = 0;//优美子数组个数和odd数组的下标
for (int i = 0; i < n; ++i) {
if ((nums[i] & 1) != 0) {
//奇偶判断,奇数二进制最后一位必为1,和1进行与运算后结果必为1,偶数为0
odd[++cnt] = i;
}
}
odd[0] = -1;//左边界处理
odd[++cnt] = n;//右边界处理
for (int i = 1; i + k <= cnt; ++i) {
ans += (odd[i] - odd[i - 1]) * (odd[i + k] - odd[i + k - 1]); //计算优美子数组总数
}
return ans;
}
}
复杂度分析
时间复杂度:O(n),其中 n为数组的大小。遍历 odd 数组最坏情况下需要 O(n) 的时间。
空间复杂度:O(n),其中 n为数组的大小。odd 数组需要 O(n)的空间。
- 前缀和、差分方法求解
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int n = nums.length;
int[] cnt = new int[n + 1];
int odd = 0, ans = 0;
cnt[0] = 1;
for (int i = 0; i < n; ++i) {
odd += nums[i] & 1;
ans += odd >= k ? cnt[odd - k] : 0;
cnt[odd] += 1;
}
return ans;
}
}