【前缀和】1248、统计优美子数组

1248、统计优美子数组(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
解释:数列中不包含任何奇数,所以不存在优美子数组。

解题思路:

  解法一:暴力枚举

  暴力枚举的方法同样可以枚举出所有的子数组,判断每一个子数组中奇数的个数,从而得到结果,时间复杂度最优为O(n^2),一般会超出题目的时间限制,不再赘述。

  解法二:数学法

  因为在这里我们只关心子数组中奇数的个数,对于有多少个偶数并不关心,因此,可以建立一个oddIndex数组,用以依次存储所有奇数的下标索引,那么对于oddIndex数组,其中的每一个元素oddIndex[i],都可以作为子数组的一个起点,并且[oddIndex[i],oddIndex[i+k-1]]就满足正好有k个奇数。

  但是,不难想到此时找到的这个子数组头尾都是奇数,所以可以向左右两侧延伸,直到碰到奇数,这样找到的子数组同样是包含k个奇数,因为只是扩展了一些偶数。

  而扩展之后的优美子数组个数=(左边的偶数个数+1)*(右边的偶数个数+1),扩展同样基于oddIndex数组就可以方便的找到,这样就可以通过计算得到最终结果。

  解法三:前缀和(前缀奇数个数)

  这里我们看到的同样是一个子数组的问题,在【前缀和】和为K、和可被K整除的子数组中,我们介绍了涉及到子数组的两类问题,求最值动态规划,不是最值考虑前缀和。那么这里是子数组的个数的问题,和第560、974题非常类似,因此可以在前缀和上做一些考虑。

  不难看到,前缀和是前i个元素之和,而这里虽然不是求和,但是我们可以用前缀奇数个数来判断,用pre[i]表示前i个元素中奇数的个数,那么从 j 到 i 这个子数组的奇数个数可以表示为pre[i]-pre[j-1],因此恰好有k个奇数可以表示为:

  pre[i]-pre[j-1]==k

  这样,实际上就完全转化为了第560题,在遍历到某个数时,找到pre-k对应的出现次数即可。

  解法四:技巧转化

  如果我们将数组中的奇数用1表示,偶数用0表示,那么有k个奇数就可以完全转化为和为K的问题。

代码实现:

//解法二:数学法,时间辅助度O(n),空间复杂度O(n)
class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        //数学法
        if(nums==null || nums.length==0)
            return 0;
        
        int res=0,len=nums.length;
        int count=0;
        int[] oddIndex=new int[len+2];
        for(int i=0;i<len;i++){
            if(nums[i]%2!=0){  //奇数
                oddIndex[++count]=i;
            }
        }

        //添加两个边界值
        oddIndex[0]=-1;
        oddIndex[++count]=len;

        for(int i=1;i+k<=count;i++){
            res += (oddIndex[i]-oddIndex[i-1])*(oddIndex[i+k]-oddIndex[i+k-1]);
        }
        return res;
    }
}

//解法三:前缀和(前缀奇数个数),时间辅助度O(n),空间复杂度O(n)
class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        //前缀和(前缀奇数个数)+哈希表,遍历过程中统计奇数的个数,pre[i]-pre[j-1]=k
        if(nums==null || nums.length==0)
            return 0;
        
        Map<Integer,Integer> map=new HashMap<>();
        map.put(0,1);  //前缀为0个奇数

        int pre=0;
        int res=0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]%2!=0) //奇数
                pre++;
            res+=map.getOrDefault(pre-k,0);
            map.put(pre,map.getOrDefault(pre,0)+1);
        }
        return res;
    }
}
posted @ 2020-05-30 00:08  gzshan  阅读(447)  评论(0编辑  收藏  举报