leetcode题解-1248.统计[优美子数组]


博客专栏地址: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

思路解析

  1. 首先看清楚题目,题目要的是连续子数组,而不是子序列,这两个的难度可不一样。
  2. 题目首先需要找到一个区间,使得这个区间内有k个奇数。这种在一个数组内找两个数(左右区间嘛,肯定就是两个数),很亲切,优先考虑尺取法~
  3. 尺取法先定好两个指针,命名为left、right,left从0出发,找到第一个奇数就停止。right从left出发,总共找到k个奇数就停止,这样区间[left, right]就有k个奇数了。
  4. 找到区间了,然后要怎么做呢?首先肯定要知道这个区间的左边有几个连续偶数,右边有几个连续偶数,然后就是推公式了。推公式也简单,多列几组数据找规律就好了。
    1. 首先对于左右的个数都是0的,很明显答案只有1个。我们记一下: f ( 0 , 0 ) = 1 f(0, 0) = 1 f(0,0)=1
    2. 左边有1个,右边有0个,那么答案就是2(左边取0个数、1个数这两种情况),记 f ( 1 , 0 ) = 2 f(1, 0) = 2 f(1,0)=2
    3. 左边有2个,右边有0个,那么答案就是3(左边分别取0、1、2个数),记 f ( 2 , 0 ) = 3 f(2, 0) = 3 f(2,0)=3
    4. 规律是不是出来了?左边有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
    5. 然后再看左边有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
    6. 整体规律是不是出来了咧?左边有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的情况。
  5. 公式出来了,那答案也就出来了,每次找到一个包含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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值