滑动窗口题目:K 个不同整数的子数组
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:K 个不同整数的子数组
出处:992. K 个不同整数的子数组
难度
7 级
题目描述
要求
给定一个整数数组 nums\texttt{nums}nums 和一个整数 k\texttt{k}k,返回 nums\texttt{nums}nums 中的好子数组的数目。
好子数组是恰好有 k\texttt{k}k 个不同整数的数组。
- 例如,[1,2,3,1,2]\texttt{[1,2,3,1,2]}[1,2,3,1,2] 中有 3\texttt{3}3 个不同的整数:1\texttt{1}1、2\texttt{2}2 和 3\texttt{3}3。
子数组是数组的连续部分。
示例
示例 1:
输入:nums=[1,2,1,2,3],k=2\texttt{nums = [1,2,1,2,3], k = 2}nums = [1,2,1,2,3], k = 2
输出:7\texttt{7}7
解释:恰好由 2\texttt{2}2 个不同整数组成的子数组:[1,2]\texttt{[1,2]}[1,2]、[2,1]\texttt{[2,1]}[2,1]、[1,2]\texttt{[1,2]}[1,2]、[2,3]\texttt{[2,3]}[2,3]、[1,2,1]\texttt{[1,2,1]}[1,2,1]、[2,1,2]\texttt{[2,1,2]}[2,1,2]、[1,2,1,2]\texttt{[1,2,1,2]}[1,2,1,2]。
示例 2:
输入:nums=[1,2,1,3,4],k=3\texttt{nums = [1,2,1,3,4], k = 3}nums = [1,2,1,3,4], k = 3
输出:3\texttt{3}3
解释:恰好由 3\texttt{3}3 个不同整数组成的子数组:[1,2,1,3]\texttt{[1,2,1,3]}[1,2,1,3]、[2,1,3]\texttt{[2,1,3]}[2,1,3]、[1,3,4]\texttt{[1,3,4]}[1,3,4]。
数据范围
- 1≤nums.length≤2×104\texttt{1} \le \texttt{nums.length} \le \texttt{2} \times \texttt{10}^\texttt{4}1≤nums.length≤2×104
- 1≤nums[i],k≤nums.length\texttt{1} \le \texttt{nums[i], k} \le \texttt{nums.length}1≤nums[i], k≤nums.length
解法
思路和算法
考虑数组 nums\textit{nums}nums 的下标范围 [start1,end][\textit{start}_1, \textit{end}][start1,end] 和 [start2,end][\textit{start}_2, \textit{end}][start2,end] 的两个子数组,其中 start1<start2\textit{start}_1 < \textit{start}_2start1<start2。如果这两个子数组的不同整数个数都是 kkk,则对于任意 start1<start3<start2\textit{start}_1 < \textit{start}_3 < \textit{start}_2start1<start3<start2,下标范围 [start3,end][\textit{start}_3, \textit{end}][start3,end] 的子数组的不同整数个数也是 kkk。理由如下。
已知下标范围 [start1,end][\textit{start}_1, \textit{end}][start1,end] 的子数组和下标范围 [start2,end][\textit{start}_2, \textit{end}][start2,end] 的子数组的不同整数个数都是 kkk。用 k′k'k′ 表示下标范围 [start3,end][\textit{start}_3, \textit{end}][start3,end] 的子数组的不同整数个数。由于下标范围 [start3,end][\textit{start}_3, \textit{end}][start3,end] 的子数组比下标范围 [start2,end][\textit{start}_2, \textit{end}][start2,end] 的子数组多了下标范围 [start3,start2−1][\textit{start}_3, \textit{start}_2 - 1][start3,start2−1] 的元素,因此 k′≥kk' \ge kk′≥k。由于下标范围 [start3,end][\textit{start}_3, \textit{end}][start3,end] 的子数组比下标范围 [start1,end][\textit{start}_1, \textit{end}][start1,end] 的子数组少了下标范围 [start1,start3−1][\textit{start}_1, \textit{start}_3 - 1][start1,start3−1] 的元素,因此 k′≤kk' \le kk′≤k。由于 k′≥kk' \ge kk′≥k 和 k′≤kk' \le kk′≤k 同时成立,因此 k′=kk' = kk′=k。
因此,以 end\textit{end}end 作为结束下标且有 kkk 个不同整数的子数组的开始下标是连续的。只要得到每个结束下标对应的有 kkk 个不同整数的子数组中的最长子数组和最短子数组,即可得到有 kkk 个不同整数的子数组的数目。
同理可得,当 end1<end2\textit{end}_1 < \textit{end}_2end1<end2 时,以 end1\textit{end}_1end1 作为结束下标且有 kkk 个不同整数的最长子数组的开始下标小于等于以 end2\textit{end}_2end2 作为结束下标且有 kkk 个不同整数的最长子数组的开始下标,以 end1\textit{end}_1end1 作为结束下标且有 kkk 个不同整数的最短子数组的开始下标小于等于以 end2\textit{end}_2end2 作为结束下标且有 kkk 个不同整数的最短子数组的开始下标。
可以使用两个变长滑动窗口实现。用 [start1,end][\textit{start}_1, \textit{end}][start1,end] 表示寻找有 kkk 个不同整数的子数组中的最长子数组的滑动窗口,用 [start2,end][\textit{start}_2, \textit{end}][start2,end] 表示寻找有 kkk 个不同整数的子数组中的最短子数组的滑动窗口,初始时 start1=start2=end=0\textit{start}_1 = \textit{start}_2 = \textit{end} = 0start1=start2=end=0。将两个滑动窗口的右端点 end\textit{end}end 向右移动,移动过程中维护两个滑动窗口的左端点 start1\textit{start}_1start1 和 start2\textit{start}_2start2。
使用两个哈希表分别记录两个滑动窗口中的每个元素的出现次数,两个哈希表分别为最长子数组哈希表和最短子数组哈希表。对于每个右端点 end\textit{end}end,执行如下操作。
-
将 nums[end]\textit{nums}[\textit{end}]nums[end] 在两个哈希表中的次数各加 111。
-
如果最长子数组哈希表中的元素个数大于 kkk,则将 nums[start1]\textit{nums}[\textit{start}_1]nums[start1] 在最长子数组哈希表中的次数减 111,如果 nums[start1]\textit{nums}[\textit{start}_1]nums[start1] 在最长子数组哈希表中的次数变成 000 则将 nums[start1]\textit{nums}[\textit{start}_1]nums[start1] 从最长子数组哈希表中删除,然后将 start1\textit{start}_1start1 向右移动一位,重复该操作直到最长子数组哈希表中的元素个数小于等于 kkk。
-
如果最短子数组哈希表中的元素个数大于等于 kkk,则将 nums[start2]\textit{nums}[\textit{start}_2]nums[start2] 在最短子数组哈希表中的次数减 111,如果 nums[start2]\textit{nums}[\textit{start}_2]nums[start2] 在最短子数组哈希表中的次数变成 000 则将 nums[start2]\textit{nums}[\textit{start}_2]nums[start2] 从最短子数组哈希表中删除,然后将 start2\textit{start}_2start2 向右移动一位,重复该操作直到最短子数组哈希表中的元素个数小于 kkk。
-
下标范围 [start1,end][\textit{start}_1, \textit{end}][start1,end] 的子数组为以 end\textit{end}end 作为结束下标且有 kkk 个不同整数的最长子数组,下标范围 [start2−1,end][\textit{start}_2 - 1, \textit{end}][start2−1,end] 的子数组为以 end\textit{end}end 作为结束下标且有 kkk 个不同整数的最短子数组,因此以 end\textit{end}end 作为结束下标且有 kkk 个不同整数的子数组的数目是 start2−start1\textit{start}_2 - \textit{start}_1start2−start1,将 start2−start1\textit{start}_2 - \textit{start}_1start2−start1 加到答案中。
遍历结束之后,即可得到数组 nums\textit{nums}nums 中的有 kkk 个不同整数的子数组的数目。
实现方面,由于数组 nums\textit{nums}nums 中的元素都是不超过 nums\textit{nums}nums 的长度的正整数,因此可以创建两个长度为 nums\textit{nums}nums 的长度加 111 的数组代替两个哈希表,使用两个计数器分别表示每个哈希表中的元素个数。初始时,计数器的值为 000。每次更新元素在哈希表中的次数之后,需要更新计数器的值,更新方法如下。
-
当一个元素在哈希表中的次数加 111 之后,如果次数变成 111,则将计数器的值加 111。
-
当一个元素在哈希表中的次数减 111 之后,如果次数变成 000,则将计数器的值减 111。
代码
class Solution {public int subarraysWithKDistinct(int[] nums, int k) {int subarrays = 0;int length = nums.length;int[] counts1 = new int[length + 1];int[] counts2 = new int[length + 1];int distinct1 = 0, distinct2 = 0;int start1 = 0, start2 = 0, end = 0;while (end < length) {int curr = nums[end];counts1[curr]++;if (counts1[curr] == 1) {distinct1++;}counts2[curr]++;if (counts2[curr] == 1) {distinct2++;}while (distinct1 > k) {int prev1 = nums[start1];counts1[prev1]--;if (counts1[prev1] == 0) {distinct1--;}start1++;}while (distinct2 >= k) {int prev2 = nums[start2];counts2[prev2]--;if (counts2[prev2] == 0) {distinct2--;}start2++;}subarrays += start2 - start1;end++;}return subarrays;}
}
复杂度分析
-
时间复杂度:O(n)O(n)O(n),其中 nnn 是数组 nums\textit{nums}nums 的长度。每个滑动窗口的左右端点最多各遍历数组 nums\textit{nums}nums 一次。
-
空间复杂度:O(n)O(n)O(n),其中 nnn 是数组 nums\textit{nums}nums 的长度。空间复杂度主要取决于哈希表,由于数组 nums\textit{nums}nums 中的元素都是不超过 nums\textit{nums}nums 的长度的正整数,因此每个哈希表中最多有 nnn 个元素。