题目链接
题目描述
给你一个由 正 整数组成的数组 nums 。
如果数组中的某个子数组满足下述条件,则称之为 完全子数组 :
- 子数组中 不同 元素的数目等于整个数组不同元素的数目。
返回数组中 完全子数组 的数目。
子数组 是数组中的一个连续非空序列。
分析
不同元素的数目 即 元素的种类数量。因此, 完全子数组 其实就是包含 nums 中各不同数字 至少一个 的子数组。
显然,若子数组 [left, right] 是 完全子数组 ,那么子数组 [left, r], (right < r < nums.length) 也是 完全子数组 。若子数组 [left, right] 不是 完全子数组,那么它的所有 子数组 全都不是 完全子数组。即 子数组长度 与 满足题目条件 之间存在 单调性。
解法
先遍历一遍 nums,统计总共有多少种数字。再将所有子数组分类为 以下标0开头的 、 以下标1开头的 ...,即采用 滑动窗口 ,逐步右移子数组的 左边界,分别统计左边界确定的条件下, 完全子数组 的数目,最后累加即可。
复杂度
- 时间复杂度:
- 空间复杂度:
代码
/**
* @param {number[]} nums
* @return {number}
*/
var countCompleteSubarrays = function(nums) {
// 统计总共的种数
const typeCount = (new Set(nums)).size;
let res = 0;
const numberRecord = new Map();
// 记录窗口的当前状态, key为数字,value为该数字在窗口中出现的次数。显然,map.size为数字的种类
for(let left = 0, right = 0; left < nums.length; left++) {
while(numberRecord.size < typeCount && right < nums.length) {
// right入窗口
numberRecord.set(nums[right], (numberRecord.get(nums[right]) ?? 0) + 1);
right++;
}
if(numberRecord.size < typeCount) {
// 后续已无满足要求的子数组
break;
}
res += nums.length - right + 1;
// left出窗口
const leftNumberCount = numberRecord.get(nums[left]);
if(leftNumberCount === 1) {
numberRecord.delete(nums[left]);
} else {
numberRecord.set(nums[left], leftNumberCount - 1);
}
}
return res;
};