题目:
给定一个正整数数组 nums和一个整数 k ,返回 num 中 「好子数组」 **的数目。
如果 nums 的某个子数组中不同整数的个数恰好为 k,则称 nums 的这个连续、不一定不同的子数组为 「 好子数组 」。
- 例如,
[1,2,3,1,2]中有3个不同的整数:1,2,以及3。
子数组 是数组的 连续 部分。
算法:
方法一:双指针
我们固定有边界j,找到满足k个不同整数的好数组左边界lower,满足k - 1个不同整数的好数组左边界upper,upper-lower就是以j为右边界,存在“k个不同整数的好数组”数量。
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
}