博客专栏地址:https://blog.csdn.net/feng964497595/category_9848847.html
github地址:https://github.com/mufeng964497595/leetcode
题目描述
给你一个整数数组 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
思路解析
- 首先看清楚题目,题目要的是连续子数组,而不是子序列,这两个的难度可不一样。
- 题目首先需要找到一个区间,使得这个区间内有k个奇数。这种在一个数组内找两个数(左右区间嘛,肯定就是两个数),很亲切,优先考虑尺取法~
- 尺取法先定好两个指针,命名为left、right,left从0出发,找到第一个奇数就停止。right从left出发,总共找到k个奇数就停止,这样区间[left, right]就有k个奇数了。
- 找到区间了,然后要怎么做呢?首先肯定要知道这个区间的左边有几个连续偶数,右边有几个连续偶数,然后就是推公式了。推公式也简单,多列几组数据找规律就好了。
- 首先对于左右的个数都是0的,很明显答案只有1个。我们记一下: f ( 0 , 0 ) = 1 f(0, 0) = 1 f(0,0)=1
- 左边有1个,右边有0个,那么答案就是2(左边取0个数、1个数这两种情况),记 f ( 1 , 0 ) = 2 f(1, 0) = 2 f(1,0)=2
- 左边有2个,右边有0个,那么答案就是3(左边分别取0、1、2个数),记 f ( 2 , 0 ) = 3 f(2, 0) = 3 f(2,0)=3
- 规律是不是出来了?左边有n个数,右边有0个数,那么答案就是n+1,记 f ( n , 0 ) = n + 1 f(n, 0) = n+1 f(n,0)=n+1左右都是对称的,同理, f ( 0 , m ) = m + 1 f(0, m) = m+1 f(0,m)=m+1
- 然后再看左边有1个,右边有1个,其实就是左边取0、1,右边取0、1,然后组合起来,也就是说左边有两种情况,右边有两种情况,组合起来就是2*2 = 4,记 f ( 1 , 1 ) = ( 1 + 1 ) ∗ ( 1 + 1 ) = 4 f(1, 1) = (1+1) * (1+1) = 4 f(1,1)=(1+1)∗(1+1)=4
- 整体规律是不是出来了咧?左边有n个,右边有m个,即左边有n+1种情况,右边有m+1种情况,组合起来就是(n+1) * (m+1)。记 f ( n , m ) = ( n + 1 ) ∗ ( m + 1 ) f(n, m) = (n+1) * (m+1) f(n,m)=(n+1)∗(m+1)。这条公式也满足上面有一遍取0的情况。
- 公式出来了,那答案也就出来了,每次找到一个包含k个奇数的最小区间,然后看左边有几个连续偶数,右边有几个连续偶数,再套公式计算就可以了。
示例代码
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int size = nums.size();
int left = 0, right = 0;
int last_left = -1;
bool first = true;
int ans = 0;
while (left <= right && right < size) {
// 找下一个奇数
while (left < size && 0 == (nums[left] & 1)) ++left;
if (left >= size) break; // 没有奇数了
// 找以这个奇数为开头的第k个奇数
if (first) {
// 第一次需要找出k个奇数的区间
first = false;
int current = 1; // 当前有1个奇数
right = left;
while (current < k) {
++right;
if (right >= size) break;
if (nums[right] & 1) ++current;
}
if (right >= size) break; // 不足k个
}
// 找到了k个奇数的区间,数一下左、右各有几个连续偶数
int left_num = left - last_left - 1;
int next_right = right + 1;
while (next_right < size && 0 == (nums[next_right] & 1)) {
++next_right;
}
int right_num = next_right - right - 1;
// 套公式计算
ans += (left_num + 1) * (right_num + 1);
// 更新下次要进行操作的左右区间
// 右边下一个奇数的位置其实已经算出来了,直接更新right就好了,不用再算一次
last_left = left;
++left;
right = next_right;
}
return ans;
}
};