【题解】力扣992. K 个不同整数的子数组
题目来源
题目描述:给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定独立的子数组为好子数组。
思路
恰好包含K种不同整数的子区间 = 最多包含K种整数的子区间 - 最多包含K-1种整数的子区间

利用滑动窗口求最多包含K种整数的子区间
例:求A中由K个不同整数组成的最长子数组的长度
private static int atMostK(int[] A, int K){
int len = A.length;
int[] freq = new int[len];
int left = 0, right = 0;
int cnt = 0;
int res = 0;
while(right < len){
if(freq[A[right]] == 0){
cnt++;
}
freq[A[right]]++;
while(cnt < K){
freq[A[left]]--;
if(freq[A[left]] == 0){
cnt--;
}
left++;
}
res = Math.max(res, right-left+1);
right++;
}
return res;
}
举例而言:对于 A = [1,2,1,2,3], K = 2,我们运行上面的代码会寻找到最长子数组 [1, 2, 1, 2] 的长度为 4.
上面求的是A中由K个不同整数组成的最长子数组的长度,如果问A中最多K个不同整数组成的子数组的个数,该怎么办?
答:只需要把res = Math.max(res, right-left+1)改成res += right-left+1即可。right-left+1为区间长度。
例:对于A=[1,2,1,2,3], K=2。会得到满足题意的子数组[1,2,1,2]和[2,3]。
- 对于子数组
[1,2,1,2],它的所有子数组,满足题意的,共有1+2+3+4=10个数组- 以第一个 1 为右端点的满足题意的子数组为
[1]; - 以第一个 2 为右端点的满足题意的子数组为
[1,2],[2]; - 以第二个 1 为右端点的满足题意的子数组为
[1,2,1],[2,1],[1]; - 以第二个 2 为右端点的满足题意的子数组为
[1,2,1,2],[2,1,2],[1,2],[2];
- 以第一个 1 为右端点的满足题意的子数组为
- 对于子数组
[2,3],它的所有子数组都满足题意,共有3个子数组。- 以
2为右端点的满足题意的子数组,在上面已经统计过了,因此不要重复统计。 - 以
3为右端点的满足题意的子数组为[2, 3], [3]。
- 以
所以总的数组 A 有 12 个由 最多 2 个不同整数组成的子数组。
所以,当 right 到达一个新位置之后,把 left 调整到满足题意的位置,当前[left, right]区间内符合条件的并且以 right 为右端点的子数组个数 为 right - left + 1。当 right 指针把数组的每个位置遍历一遍,就得到了以每个位置作为区间右短点的子数组长度,累加得到的就是结果。
这个思想有点类似于动态规划,如果 dp[i] 表示以 i 为右端点的符合题意的子数组个数,那么sum(dp[0..N-1])就能求得所有子数组的个数之和。
代码
class Solution {
public int subarraysWithKDistinct(int[] A, int K) {
return mostKSubarrays(A, K) - mostKSubarrays(A, K-1);
}
private static int mostKSubarrays(int[] a, int k) {
int len = a.length;
int[] freq = new int[len+1]; // 记录数字出现的频次
int left = 0,right = 0;
int cnt = 0; // 记录不同数组的个数
int res = 0; // 返回结果
while(right < len){
if(freq[a[right]] == 0){
cnt++;
}
freq[a[right]]++; // 频次增加
while(cnt > k){ // 不同数字超过K,即不符合题意
freq[a[left]]--; // 不能与下面的left++顺序写反
if(freq[a[left]] == 0){
cnt--;
}
left++;
}
res += right - left + 1; // 累加结果
right++; // 指针右移
}
return res;
}
}
复杂度分析:
- 时间复杂度:
O(n) - 空间复杂度:
O(n)

浙公网安备 33010602011771号