题目链接
6293. 统计好子数组的数目
题目描述
给你一个整数数组 nums 和一个整数 k ,请你返回 nums 中 好 子数组的数目。
一个子数组 arr 如果有 至少 k 对下标 (i, j) 满足 i < j 且 arr[i] == arr[j] ,那么称它是一个 好 子数组。
子数组 是原数组中一段连续 非空 的元素序列。
题目示例
输入:nums = [3,1,4,3,2,2,4], k = 2
输出:4
解释:总共有 4 个不同的好子数组:
- [3,1,4,3,2,2] 有 2 对。
- [3,1,4,3,2,2,4] 有 3 对。
- [1,4,3,2,2,4] 有 2 对。
- [4,3,2,2,4] 有 2 对。
题解思路
如何快速判断一个数组中有多少对相等
观察可以发现如果数组内有2个重复的数字则有一对,3个则有3对,4个则有6对
个数 推算结果
1 0
2 0 + 1
3 0 + 1 + 2
4 0 + 1 + 2 + 3
5 0 + 1 + 2 + 3 + 4
即公式为 (cnt * (cnt - 1)) / 2
滑动窗口
右指针不断右移,当符合好数组条件时,右指针及其后面的位置都是满足好数组条件的,此时左指针右移,继续判断当前滑动窗口是否符合条件
因为左右指针最多都移动n次,所以虽然有2个while但是时间复杂度为O(n)
题目解答
我最开始的解法如下,但是超时了
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var countGood = function(nums, k) {
let left = 0, right = 0, res = 0, len = nums.length
const cache = new Map() // 统计下标[left, right]窗口内数字的个数
while (right < len) {
const numCnt = cache.get(nums[right]) || 0
cache.set(nums[right], numCnt + 1)
while (isGood(cache, k) && left < right) {
// 右指针及其后面的位置都是满足条件的
// 即[left, right] [left, right + 1] ... [left, len - 1] 这么多子数组都是满足条件的
res += len - right
cache.set(nums[left], cache.get(nums[left]) - 1)
left++
}
right++
}
return res
};
// 根据cache判断这个数组是否为好数组 时间复杂度取决于cache.size
function isGood(cache, k) {
const cnt = Array.from(cache).reduce((p, [_, value]) => {
return p + ((value - 1) + 1) * (value - 1) / 2
}, 0)
return cnt >= k
}
下面是优化后的思路
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var countGood = function(nums, k) {
let left = 0, right = 0, res = 0, len = nums.length, cnt = 0
const cache = new Map() // 统计下标[left, right]窗口内数字的个数
/**
* 出现个数 推算结果
* 1 0
* 2 0 + 1
* 3 0 + 1 + 2
* 4 0 + 1 + 2 + 3
* 5 0 + 1 + 2 + 3 + 4
*/
while (right < len) {
const numCnt = cache.get(nums[right]) || 0
cache.set(nums[right], numCnt + 1)
// 按照上面的规律 如果之前出现4次 出现5次的时候 +4 既可
cnt += numCnt
while (cnt >= k) {
// !!! 右指针及其后面的位置都是满足条件的
// 即[left, right] [left, right + 1] ... [left, len - 1] 这么多子数组都是满足条件的
res += len - right
const numCnt = cache.get(nums[left])
cache.set(nums[left], numCnt - 1)
// 按照上面的规律 如果之前出现4次 出现3次的时候 -3 既可
cnt -= numCnt - 1
left++
}
right++
}
return res
};
