问题描述
小A拿到了一个长度为nn的数组,并且定义了一个连续子数组的“权值”为该子数组内不同元素的个数。现在,她想知道,权值分别为1,2,3,…,n1,2,3,…,n的子数组数量有多少个。
你需要根据给定的数组,输出一个包含nn个整数的数组,第ii个数表示权值为ii的子数组数量。
测试样例
样例1:
输入:
n = 4, a = [1, 2, 2, 3]
输出:[5, 4, 1, 0]
样例2:
输入:
n = 3, a = [1, 1, 1]
输出:[6, 0, 0]
样例3:
输入:
n = 5, a = [1, 2, 3, 2, 1]
输出:[5, 5, 5, 0, 0]
问题分析
题目要求我们根据一个长度为 n
的数组,计算每个权值为 1
到 n
的子数组的数量。其中,子数组的“权值”定义为该子数组内不同元素的个数。
我们需要输出一个长度为 n
的数组,其中第 i
个元素表示所有“权值”为 i
的子数组的数量。
解题思路
1. 子数组的定义和权值计算:
- 一个子数组是一个连续的数组片段。
- 子数组的“权值”是指子数组中不同元素的个数。
例如,对于子数组 [1, 2, 2, 3]
,其中的不同元素有 1, 2, 3
,所以该子数组的权值为 3
。
2. 暴力解法的局限性:
我们可以通过暴力的方法,计算所有子数组的权值。对于每个子数组,计算其中不同元素的个数,统计每个权值的出现次数。然而,暴力解法会涉及到 O(n^2)
次子数组的遍历,并且每个子数组需要花费 O(n)
来计算其不同元素的个数,总体时间复杂度是 O(n^3)
,对于较大的输入来说是不可行的。
3. 优化思路:
我们可以通过使用滑动窗口(双指针)来优化计算过程。滑动窗口的基本思想是:
- 使用两个指针(
left
和right
)来表示当前子数组的范围,left
固定时,移动right
来扩展窗口。 - 利用一个字典或者哈希表来记录当前窗口内每个元素的频次,进而计算当前窗口内的不同元素的个数。
- 根据不同元素的个数更新对应的子数组权值的计数。
4. 具体步骤:
- 使用两个指针
left
和right
表示当前的子数组范围,初始化时让left = 0
,right = 0
。 - 使用一个哈希表
freq_map
来记录当前窗口内各元素的频次。 - 对于每个
right
指针的位置,计算当前窗口内不同元素的个数,并更新对应的计数。 - 滑动
left
指针,确保窗口内不同元素的个数不超过当前考虑的权值。
5. 复杂度分析:
- 时间复杂度:使用滑动窗口方法,
left
和right
都只会遍历一次整个数组,因此时间复杂度为O(n)
。 - 空间复杂度:需要使用一个哈希表来记录每个元素的频次,空间复杂度为
O(n)
。
代码实现
from collections import defaultdict
def solution(n: int, a: list) -> list:
# c[i]表示权值为i的子数组的数量
c = [0] * (n + 1)
# 用来统计每个子数组内的不同元素个数
for left in range(n):
freq_map = defaultdict(int)
distinct_count = 0
# 从left开始扩展右边的子数组
for right in range(left, n):
# 如果该元素未出现过,distinct_count增加
if freq_map[a[right]] == 0:
distinct_count += 1
freq_map[a[right]] += 1
# 更新对应的权值计数
if distinct_count <= n:
c[distinct_count] += 1
return c[1:] # 返回从权值1到n的子数组数量
# 测试样例
if __name__ == '__main__':
print(solution(4, [1, 2, 2, 3]) == [5, 4, 1, 0])
print(solution(3, [1, 1, 1]) == [6, 0, 0])
print(solution(5, [1, 2, 3, 2, 1]) == [5, 5, 5, 0, 0])
代码解释
-
初始化计数数组:
c
用来保存权值为 1 到 n 的子数组的数量。 -
滑动窗口的实现
:
- 我们通过两个指针
left
和right
遍历所有可能的子数组。 - 使用哈希表
freq_map
来记录当前窗口内每个元素的频次。 distinct_count
记录当前窗口内不同元素的个数。- 每扩展一个子数组,就更新对应的
distinct_count
的计数。
- 我们通过两个指针
-
返回结果:返回从
c[1]
到c[n]
的子数组数量,即权值从 1 到 n 的子数组数量。
时间和空间复杂度分析
- 时间复杂度:
O(n^2)
,因为我们使用了两个嵌套循环,其中外层循环是left
的遍历,内层循环是right
的遍历,对于每对left
和right
,我们更新哈希表的操作是O(1)
。因此,总的时间复杂度是O(n^2)
。 - 空间复杂度:
O(n)
,我们使用了一个哈希表来记录每个子数组的频次,最多需要存储n
个元素的频次。
代码优化
目前的解法已经通过滑动窗口和哈希表将时间复杂度优化为 O(n^2)
,这是一个比较有效的解法。由于每个子数组的计算只在固定时间内完成,因此我们无法进一步显著优化时间复杂度,除非在极大数据量下通过更复杂的数据结构进行处理。
总结
- 解题思路:使用滑动窗口和哈希表来统计每个子数组的不同元素个数,并计算每个权值的子数组数量。
- 时间复杂度:
O(n^2)
,适合较大的n
。 - 空间复杂度:
O(n)
,需要额外的空间存储哈希表。