滑动窗口题目:统计「优美子数组」
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:统计「优美子数组」
出处: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}1≤nums.length≤50000
- 1≤nums[i]≤105\texttt{1} \le \texttt{nums[i]} \le \texttt{10}^\texttt{5}1≤nums[i]≤105
- 1≤k≤nums.length\texttt{1} \le \texttt{k} \le \texttt{nums.length}1≤k≤nums.length
解法一
思路和算法
如果数组 nums\textit{nums}nums 的下标范围 [x,y][x, y][x,y] 的子数组中有 kkk 个奇数,且 nums[x]\textit{nums}[x]nums[x] 和 nums[y]\textit{nums}[y]nums[y] 都是奇数,则在不引入新的奇数的情况下,将子数组的开始下标向左移动和将子数组的结束下标向右移动之后,子数组中仍有 kkk 个奇数。假设 nums[x]\textit{nums}[x]nums[x] 左侧有连续 left\textit{left}left 个偶数,nums[y]\textit{nums}[y]nums[y] 右侧有连续 right\textit{right}right 个偶数,则包含 kkk 个奇数且这 kkk 个奇数都在下标范围 [x,y][x, y][x,y] 中的子数组个数是 (left+1)×(right+1)(\textit{left} + 1) \times (\textit{right} + 1)(left+1)×(right+1)。
对于长度为 nnn 的数组 nums\textit{nums}nums,假设 nums[−1]\textit{nums}[-1]nums[−1] 和 nums[n]\textit{nums}[n]nums[n] 都是奇数,由于 0≤x≤y<n0 \le x \le y < n0≤x≤y<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=x−x′,right+1=y′−y\textit{right} + 1 = y' - yright+1=y′−y。记 leftCount=x−x′\textit{leftCount} = x - x'leftCount=x−x′,rightCount=y′−y\textit{rightCount} = y' - yrightCount=y′−y,则包含 kkk 个奇数且这 kkk 个奇数都在下标范围 [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 和下标 nnn。用 oddCount\textit{oddCount}oddCount 表示数组 oddIndices\textit{oddIndices}oddIndices 的长度,当 0<i≤j<oddCount−10 < i \le j < \textit{oddCount} - 10<i≤j<oddCount−1 且 j−i+1=kj - i + 1 = kj−i+1=k 时,数组 oddIndices\textit{oddIndices}oddIndices 中的下标范围 [i,j][i, j][i,j] 的子数组表示数组 nums\textit{nums}nums 的一个包含 kkk 个奇数的子数组,将 (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[i−1])×(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),其中 nnn 是数组 nums\textit{nums}nums 的长度。需要创建长度为 O(n)O(n)O(n) 的数组 oddIndices\textit{oddIndices}oddIndices 记录数组 nums\textit{nums}nums 中的所有奇数所在下标,然后使用大小为 kkk 的定长滑动窗口遍历数组 oddIndices\textit{oddIndices}oddIndices 统计「优美子数组」的数目。
-
空间复杂度:O(n)O(n)O(n),其中 nnn 是数组 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} = 0start=end=0。将滑动窗口的右端点 end\textit{end}end 向右移动,直到滑动窗口中包含 kkk 个奇数(确保 nums[end]\textit{nums}[\textit{end}]nums[end] 是奇数)或者 end\textit{end}end 超出数组下标范围。
当滑动窗口中包含 kkk 个奇数时,需要计算包含 kkk 个奇数且这 kkk 个奇数都在下标范围 [start,end][\textit{start}, \textit{end}][start,end] 中的子数组个数,计算方法如下。
-
当 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。
-
将 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。
-
将 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 中的两个相邻奇数之间的距离,且确保子数组中包含 kkk 个奇数。
代码
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),其中 nnn 是数组 nums\textit{nums}nums 的长度。滑动窗口的左右端点最多各遍历数组 nums\textit{nums}nums 一次。
-
空间复杂度:O(1)O(1)O(1)。