滑动窗口题目:统计「优美子数组」

题目

标题和出处

标题:统计「优美子数组」

出处:1248. 统计「优美子数组」

难度

5 级

题目描述

要求

给定一个整数数组 nums \texttt{nums} nums 和一个整数 k \texttt{k} k。如果一个连续子数组中恰好有 k \texttt{k} k 个奇数,则该子数组是「优美子数组」。

返回「优美子数组」的数目。

示例

示例 1:

输入: nums   =   [1,1,2,1,1],   k   =   3 \texttt{nums = [1,1,2,1,1], k = 3} nums = [1,1,2,1,1], k = 3
输出: 2 \texttt{2} 2
解释:包含 3 \texttt{3} 3 个奇数的子数组是 [1,1,2,1] \texttt{[1,1,2,1]} [1,1,2,1] [1,2,1,1] \texttt{[1,2,1,1]} [1,2,1,1]

示例 2:

输入: nums   =   [2,4,6],   k   =   1 \texttt{nums = [2,4,6], k = 1} nums = [2,4,6], k = 1
输出: 0 \texttt{0} 0
解释:数列中不包含任何奇数,所以不存在优美子数组。

示例 3:

输入: nums   =   [2,2,2,1,2,2,1,2,2,2],   k   =   2 \texttt{nums = [2,2,2,1,2,2,1,2,2,2], k = 2} nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出: 16 \texttt{16} 16

数据范围

  • 1 ≤ nums.length ≤ 50000 \texttt{1} \le \texttt{nums.length} \le \texttt{50000} 1nums.length50000
  • 1 ≤ nums[i] ≤ 10 5 \texttt{1} \le \texttt{nums[i]} \le \texttt{10}^\texttt{5} 1nums[i]105
  • 1 ≤ k ≤ nums.length \texttt{1} \le \texttt{k} \le \texttt{nums.length} 1knums.length

解法一

思路和算法

如果数组 nums \textit{nums} nums 的下标范围 [ x , y ] [x, y] [x,y] 的子数组中有 k k k 个奇数,且 nums [ x ] \textit{nums}[x] nums[x] nums [ y ] \textit{nums}[y] nums[y] 都是奇数,则在不引入新的奇数的情况下,将子数组的开始下标向左移动和将子数组的结束下标向右移动之后,子数组中仍有 k k k 个奇数。假设 nums [ x ] \textit{nums}[x] nums[x] 左侧有连续 left \textit{left} left 个偶数, nums [ y ] \textit{nums}[y] nums[y] 右侧有连续 right \textit{right} right 个偶数,则包含 k k k 个奇数且这 k k k 个奇数都在下标范围 [ x , y ] [x, y] [x,y] 中的子数组个数是 ( left + 1 ) × ( right + 1 ) (\textit{left} + 1) \times (\textit{right} + 1) (left+1)×(right+1)

对于长度为 n n n 的数组 nums \textit{nums} nums,假设 nums [ − 1 ] \textit{nums}[-1] nums[1] nums [ n ] \textit{nums}[n] nums[n] 都是奇数,由于 0 ≤ x ≤ y < n 0 \le x \le y < n 0xy<n,因此 nums [ x ] \textit{nums}[x] nums[x] 左侧和 nums [ y ] \textit{nums}[y] nums[y] 右侧一定有奇数。假设 nums [ x ] \textit{nums}[x] nums[x] 左侧距离最近的奇数位于下标 x ′ x' x nums [ y ] \textit{nums}[y] nums[y] 右侧距离最近的奇数位于下标 y ′ y' y,则有 left + 1 = x − x ′ \textit{left} + 1 = x - x' left+1=xx right + 1 = y ′ − y \textit{right} + 1 = y' - y right+1=yy。记 leftCount = x − x ′ \textit{leftCount} = x - x' leftCount=xx rightCount = y ′ − y \textit{rightCount} = y' - y rightCount=yy,则包含 k k k 个奇数且这 k k k 个奇数都在下标范围 [ x , y ] [x, y] [x,y] 中的子数组个数是 leftCount × rightCount \textit{leftCount} \times \textit{rightCount} leftCount×rightCount

根据上述分析,可以创建一个数组 oddIndices \textit{oddIndices} oddIndices 记录数组 nums \textit{nums} nums 中的所有奇数所在下标,包括下标 − 1 -1 1 和下标 n n n。用 oddCount \textit{oddCount} oddCount 表示数组 oddIndices \textit{oddIndices} oddIndices 的长度,当 0 < i ≤ j < oddCount − 1 0 < i \le j < \textit{oddCount} - 1 0<ij<oddCount1 j − i + 1 = k j - i + 1 = k ji+1=k 时,数组 oddIndices \textit{oddIndices} oddIndices 中的下标范围 [ i , j ] [i, j] [i,j] 的子数组表示数组 nums \textit{nums} nums 的一个包含 k k k 个奇数的子数组,将 ( oddIndices [ i ] − oddIndices [ i − 1 ] ) × ( oddIndices [ j + 1 ] − oddIndices [ j ] ) (\textit{oddIndices}[i] - \textit{oddIndices}[i - 1]) \times (\textit{oddIndices}[j + 1] - \textit{oddIndices}[j]) (oddIndices[i]oddIndices[i1])×(oddIndices[j+1]oddIndices[j]) 加到答案中。

遍历结束之后,即可得到「优美子数组」的数目。

代码

class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        int subarrays = 0;
        List<Integer> oddIndices = new ArrayList<Integer>();
        oddIndices.add(-1);
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (nums[i] % 2 == 1) {
                oddIndices.add(i);
            }
        }
        oddIndices.add(n);
        int oddCount = oddIndices.size();
        for (int i = 1, j = k; j < oddCount - 1; i++, j++) {
            int leftCount = oddIndices.get(i) - oddIndices.get(i - 1);
            int rightCount = oddIndices.get(j + 1) - oddIndices.get(j);
            subarrays += leftCount * rightCount;
        }
        return subarrays;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要创建长度为 O ( n ) O(n) O(n) 的数组 oddIndices \textit{oddIndices} oddIndices 记录数组 nums \textit{nums} nums 中的所有奇数所在下标,然后使用大小为 k k k 的定长滑动窗口遍历数组 oddIndices \textit{oddIndices} oddIndices 统计「优美子数组」的数目。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要创建长度为 O ( n ) O(n) O(n) 的数组 oddIndices \textit{oddIndices} oddIndices 记录数组 nums \textit{nums} nums 中的所有奇数所在下标。

解法二

思路和算法

解法一创建新数组记录所有奇数所在下标,然后在新数组上使用定长滑动窗口统计「优美子数组」的数目。也可以在数组 nums \textit{nums} nums 上使用变长滑动窗口统计「优美子数组」的数目。

[ start , end ] [\textit{start}, \textit{end}] [start,end] 表示滑动窗口,初始时 start = end = 0 \textit{start} = \textit{end} = 0 start=end=0。将滑动窗口的右端点 end \textit{end} end 向右移动,直到滑动窗口中包含 k k k 个奇数(确保 nums [ end ] \textit{nums}[\textit{end}] nums[end] 是奇数)或者 end \textit{end} end 超出数组下标范围。

当滑动窗口中包含 k k k 个奇数时,需要计算包含 k k k 个奇数且这 k k k 个奇数都在下标范围 [ start , end ] [\textit{start}, \textit{end}] [start,end] 中的子数组个数,计算方法如下。

  1. nums [ start ] \textit{nums}[\textit{start}] nums[start] 是偶数时,将 start \textit{start} start 向右移动,直到 start \textit{start} start 是奇数,然后将 start \textit{start} start 再次向右移动一位,将 start \textit{start} start 的移动次数记为 leftCount \textit{leftCount} leftCount

  2. end \textit{end} end 向右移动一位,当 end \textit{end} end 未超出数组下标范围且 nums [ end ] \textit{nums}[\textit{end}] nums[end] 是偶数时,将 end \textit{end} end 向右移动,直到 end \textit{end} end 超出数组下标范围或 nums [ end ] \textit{nums}[\textit{end}] nums[end] 是奇数,将 end \textit{end} end 的移动次数记为 rightCount \textit{rightCount} rightCount

  3. leftCount × rightCount \textit{leftCount} \times \textit{rightCount} leftCount×rightCount 加到答案中。

end \textit{end} end 超出数组下标范围时,遍历结束,此时即可得到「优美子数组」的数目。

上述过程中,每一轮 start \textit{start} start 向右移动时都经过一个奇数,然后停留在该奇数右边的相邻元素,每一轮 end \textit{end} end 向右移动时都是从一个奇数移动到下一个奇数(这里将超出数组下标范围的元素也看成奇数)。因此,每次 start \textit{start} start end \textit{end} end 的移动次数都是数组 nums \textit{nums} nums 中的两个相邻奇数之间的距离,且确保子数组中包含 k k k 个奇数。

代码

class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        int oddCount = 0;
        int start = 0, end = 0;
        int n = nums.length;
        while (end < n) {
            if (nums[end] % 2 == 1) {
                oddCount++;
                if (oddCount == k) {
                    break;
                }
            }
            end++;
        }
        int subarrays = 0;
        while (end < n) {
            int leftCount = 1, rightCount = 1;
            while (nums[start] % 2 == 0) {
                leftCount++;
                start++;
            }
            start++;
            end++;
            while (end < n && nums[end] % 2 == 0) {
                rightCount++;
                end++;
            }
            subarrays += leftCount * rightCount;
        }
        return subarrays;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。滑动窗口的左右端点最多各遍历数组 nums \textit{nums} nums 一次。

  • 空间复杂度: O ( 1 ) O(1) O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值