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

144 阅读2分钟

题目:
给定一个正整数数组 nums和一个整数 k ,返回 num 中 「好子数组」 **的数目。

如果 nums 的某个子数组中不同整数的个数恰好为 k,则称 nums 的这个连续、不一定不同的子数组为  好子数组 」

  • 例如,[1,2,3,1,2] 中有 3 个不同的整数:12,以及 3

子数组 是数组的 连续 部分。

算法:

方法一:双指针
我们固定有边界j,找到满足k个不同整数的好数组左边界lower,满足k - 1个不同整数的好数组左边界upper,upper-lower就是以j为右边界,存在“k个不同整数的好数组”数量。
image.png

func subarraysWithKDistinct(nums []int, k int) int {
    n := len(nums)
    ans := 0
    upper := make([]int, n )
    lower := make([]int, n )
    // 对于nums中的每个元素为右边界,找到“子数组中不同整数的个数恰好为 k/k-1”的左边界,保存在lower/upper中
    findLeft(upper, nums, k - 1)
    findLeft(lower, nums, k)
    fmt.Println(lower, upper)
    for i := range nums {
        ans = ans + upper[i] - lower[i]
    }
    return ans
}

// arr保存以index为右边界,val为不同整数长度为k“好子数组”的左边界
// 不存在则value == index
func findLeft(arr, nums []int, k int) {
    windowDistinctNumCount := 0
    numCount := make([]int, len(nums) + 1)
    for left, right := 0, 0; right < len(nums); right ++ {
        if numCount[nums[right]] == 0 {
            windowDistinctNumCount ++
        }
        numCount[nums[right]] ++
        for windowDistinctNumCount > k {
            if numCount[nums[left]] == 1 {
                windowDistinctNumCount --
            }
            numCount[nums[left]] --
            left ++
        }
        arr[right] = left
    }
}

另一种写法:
固定左边界情况下,恰好存在k个不同数字的区间数 = 最多存在k个不同数字的区间数 - 最多存在k - 1个不同数字的区间数(这你怎么想到的?最多k个-最多k -1个=恰好k个)

func subarraysWithKDistinct(nums []int, k int) int {
    return findAtMostKDistinct(nums, k) - findAtMostKDistinct(nums, k - 1)
}

// 返回不同整数个数小于等于k,的好数组个数
func findAtMostKDistinct(nums []int, k int) int {
    n := len(nums)
    ans := 0
    windowDistinctNumCount := 0
    numCount := make([]int, n + 1)
    for left, right := 0, 0; right < n; right ++ {
        if numCount[nums[right]] == 0 {
            windowDistinctNumCount ++
        }
        numCount[nums[right]] ++
        
        for windowDistinctNumCount > k {
            if numCount[nums[left]] == 1 {
                windowDistinctNumCount --
            }
            numCount[nums[left]] --
            left ++
        }
        // windowDistinctNumCount小于等于k
        ans = ans + right + 1 - left
    }
    return ans
}

另一种更好理解的方法

func subarraysWithKDistinct(nums []int, k int) int {
    ans := 0
    n := len(nums)
    // 统计长度为k的好子数组各数字的出现次数
    numsCount1 := make([]int, n + 1)
    // 统计长度为k - 1的好子数组各数字的出现次数
    numsCount2 := make([]int, n + 1)
    var left1, left2, distinctNumCount1, distinctNumCount2 int
    // 对于每一个right位置,找到包含k个不同整数的左边界left1,包含k - 1个不同整数的左边界left2。left2 - left1就是以right为右边界,包含k个不同整数子数组的个数。
    for right := 0; right < n; right ++ {
        if numsCount1[nums[right]] == 0 {
            distinctNumCount1 ++
        }
        numsCount1[nums[right]] ++

        if numsCount2[nums[right]] == 0 {
            distinctNumCount2 ++
        }
        numsCount2[nums[right]] ++

        for distinctNumCount1 > k {
            numsCount1[nums[left1]] -- 
            if numsCount1[nums[left1]] == 0 {
                distinctNumCount1 -- 
            }
            left1 ++
        }

        for distinctNumCount2 > k - 1 {
            numsCount2[nums[left2]] -- 
            if numsCount2[nums[left2]] == 0 {
                distinctNumCount2 -- 
            }
            left2 ++
        }
        // 以right为有边界,left1,left2都没找到的情况下,left1,left2=0
        // left2找到,left1没找到,此时还是left1,left2=0
        // left2,1都找到了,那么上面的两个for循环时钟保持left2,left1的有效性。
        ans = ans + left2 - left1
    }
    return ans
}