LeetCode每日一题2025-04-24
2799. 统计完全子数组的数目 M
给你一个由 正 整数组成的数组 nums 。
如果数组中的某个子数组满足下述条件,则称之为 完全子数组 :
- 子数组中 不同 元素的数目等于整个数组不同元素的数目。
返回数组中 完全子数组 的数目。
子数组 是数组中的一个连续非空序列。
示例 1:
输入:nums = [1,3,1,2,2]
输出:4
解释:完全子数组有:[1,3,1,2]、[1,3,1,2,2]、[3,1,2] 和 [3,1,2,2] 。
示例 2:
输入:nums = [5,5,5,5]
输出:10
解释:数组仅由整数 5 组成,所以任意子数组都满足完全子数组的条件。子数组的总数为 10 。
提示:
1 <= nums.length <= 10001 <= nums[i] <= 2000
问题分析
-
给定正整数数组
nums,长度为n()。 -
记数组
nums中 不同 元素的总数为 。 -
我们需要统计「子数组中不同元素数 等于」的子数组个数。
算法思路
暴力枚举:枚举左端点 ,再枚举右端点 ,对每个子数组 nums[i:j+1] 用哈希或 set 统计不同元素数,再对比是否等于 。
时间复杂度:
- 枚举子数组一共
- 每个子数组去重也要
- 合计 ,在 时是不可行的( 级别的操作)。
暴力+优化也可通过测试:采用双重循环遍历所有可能的子数组,并检查每个子数组是否符合条件,对于每一个起始索引
i,逐步扩展结束索引j,使用集合记录当前子数组中的元素。一旦集合大小达到整个数组的不同元素数量,则计数加一;若超过该数量则提前终止内层循环,时间复杂度最坏为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 class Solution:
def countCompleteSubarrays(self, nums: List[int]) -> int:
total_unique = len(set(nums))
if total_unique == 0:
return 0
K = total_unique
result = 0
n = len(nums)
for i in range(n):
current_set = set()
for j in range(i, n):
current_set.add(nums[j])
if len(current_set) == K:
result += 1
elif len(current_set) > K:
break # 一旦超出所需的唯一计数,就不需要检查其他元素
return result
我们令函数
则恰好等于 的子数组数目
所有「不同数 」的子数组共 个,
但其中也包括「不同数 」的 个,
二者相减,即剩下「不同数恰好 = 」的部分。
使用滑动窗口 + 双指针:
- 窗口定义
- 左指针
i,右指针j,初始都指向 0。 - 窗口
nums[i…j]内维护哈希表count,记录每个数字出现的次数。 - 变量
distinct表示当前窗口内 不同 元素的个数。
- 左指针
- 右指针扩展
- 遍历
j从 0 到 :- 将
nums[j]加入窗口:若原来count[nums[j]]==0,则distinct += 1;再count[nums[j]] += 1。
- 将
- 遍历
- 左指针收缩
- 当
distinct > k时,需要不断移动左指针i:- 从窗口移出
nums[i]:count[nums[i]] -= 1;若count[nums[i]]变为 0,则distinct -= 1;然后i += 1
- 从窗口移出
- 收缩后,窗口再次保证
distinct ≤ k。
- 当
- 计数子数组
- 对于每一个新的
j位置,当前窗口 所包含的所有合法子数组(以j为右端)数量就是窗口长度:j - i + 1。- 因为任意左端点 满足 ,子数组
nums[L…j]都有distinct ≤ k。
- 因为任意左端点 满足 ,子数组
- 对于每一个新的
- 累加结果
- 对每个
j,将j - i + 1累加到res,最终res = F(k)。
- 对每个
以 nums = [1,3,1,2,2], 为例,计算「不同数 」的子数组数:
| 步骤 | j | 加入 nums[j] | window | distinct | i | 新增子数组数 j−i+1 | 累计 res |
|---|---|---|---|---|---|---|---|
| 初始化 | — | — | [] | 0 | 0 | — | 0 |
| 1 | 0 | 1 | [1] | 1 | 0 | 0−0+1 = 1 | 1 |
| 2 | 1 | 3 | [1,3] | 2 | 0 | 1−0+1 = 2 | 3 |
| 3 | 2 | 1 | [1,3,1] | 2 | 0 | 2−0+1 = 3 | 6 |
| 4 | 3 | 2 → distinct=3 | [1,3,1,2] → 收缩 → [3,1,2] | 2 → 经过收缩 | 1 | 3−1+1 = 3 | 9 |
| 5 | 4 | 2 | [3,1,2,2] | 3→收缩→2 | 2 | 4−2+1 = 3 | 12 |
最终我们得到 F(2) = 12。
类似地可以算出 F(1),最后 F(2) - F(1) 就是「不同数恰好 」的子数组数。
时间复杂度
- 时间复杂度:一次
atMost为 ,每个元素最多进出窗口各一次共两次,故 - 空间复杂度:哈希表存储最多 个键,故
代码分解
-
用「」和「」之差得到「」。
-
滑动窗口维护「不同元素数 」的区间,通过左右指针一遍扫描完成。
-
累加
(j - i + 1)即可统计当前右端下所有合法子数组。
代码实现
1 | class Solution: |
