题目
标题和出处
标题:统计「优美子数组」
难度
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] ≤ 10 5 \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] 的子数组中有 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 0≤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' - y right+1=y′−y。记 leftCount = x − x ′ \textit{leftCount} = x - x' leftCount=x−x′, rightCount = y ′ − y \textit{rightCount} = y' - y rightCount=y′−y,则包含 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<i≤j<oddCount−1 且 j − i + 1 = k j - i + 1 = k j−i+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[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),其中 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] 中的子数组个数,计算方法如下。
-
当 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 中的两个相邻奇数之间的距离,且确保子数组中包含 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)。
419

被折叠的 条评论
为什么被折叠?



