【题解】力扣992. K 个不同整数的子数组

题目来源

992. K 个不同整数的子数组

题目描述:给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定独立的子数组为好子数组

思路

恰好包含K种不同整数的子区间 = 最多包含K种整数的子区间 - 最多包含K-1种整数的子区间

图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];
  • 对于子数组[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)

参考资料:

  1. 力扣官方题解:🎦 K 个不同整数的子数组
  2. @负雪明烛
posted @ 2021-02-10 15:54  zzzzzy2k  阅读(99)  评论(0编辑  收藏  举报