一、小A的子数组权值
问题描述
小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]
二、解题思路
(1)问题理解
题目要求我们计算一个长度为 n 的数组中,所有可能的连续子数组的权值,并统计每个权值的子数组数量。这里的“权值”定义为子数组中不同元素的个数。我们需要输出一个长度为 n 的数组,其中第 i 个元素表示权值为 i 的子数组数量。
(2)数据结构选择
1.集合(Set) :用于记录当前子数组中的不同元素。集合的特性是自动去重,因此可以方便地计算不同元素的个数。
2.数组(Array) :用于存储每个权值的子数组数量。数组的长度为 n+1,其中 c[i] 表示权值为 i 的子数组数量。
(3)算法步骤
1.初始化计数数组 c
创建一个长度为 n+1 的数组 c,所有元素初始化为0。这个数组用于记录每个权值的子数组数量。
2.双层循环遍历所有子数组
外层循环 i 从0到 n-1,表示子数组的起始位置。内层循环 j 从 i 到 n-1,表示子数组的结束位置。
3.使用集合 seen 记录当前子数组中的不同元素
每次将 a[j] 加入集合 seen。计算当前子数组的权值 weight,即集合 seen 的大小。如果 weight 小于等于 n,则将 c[weight] 加1。
4.返回结果
返回 c[1:],即权值为1到 n 的子数组数量。
三、完整代码
def solution(n: int, a: list) -> list:
c = [0 for _ in range(n + 1)]
# 遍历所有可能的子数组
for i in range(n):
seen = set()
for j in range(i, n):
seen.add(a[j])
weight = len(seen)
if weight <= n:
c[weight] += 1
else:
break
return c[1:]
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])
四、对代码的分析
(1)初始化计数数组 c
我们创建一个长度为 n+1 的数组 c,所有元素初始化为0。这个数组用于记录每个权值的子数组数量。例如,c[1] 表示权值为1的子数组数量,c[2] 表示权值为2的子数组数量,依此类推。
(2)双层循环遍历所有子数组
外层循环 i 从0到 n-1,表示子数组的起始位置。对于每个起始位置 i,我们遍历从 i 到 n-1 的所有可能的结束位置 j。
内层循环 j 从 i 到 n-1,表示子数组的结束位置。对于每个结束位置 j,我们计算从 i 到 j 的子数组的权值。
(3)使用集合 seen 记录当前子数组中的不同元素
在每次内层循环中,我们将 a[j] 加入集合 seen。集合的特性是自动去重,因此集合的大小就是当前子数组中不同元素的个数。
计算当前子数组的权值 weight,即集合 seen 的大小。
如果 weight 小于等于 n,则将 c[weight] 加1。这是因为我们只关心权值在1到 n 之间的子数组数量。
(4)返回结果
最后,我们返回 c[1:],即权值为1到 n 的子数组数量。这是因为题目要求我们输出一个长度为 n 的数组,其中第 i 个元素表示权值为 i 的子数组数量。
五、复杂度分析
时间复杂度:双层循环的时间复杂度为 O(n^2),其中 n 是数组的长度。对于每个子数组,我们使用集合来计算不同元素的个数,集合操作的时间复杂度为 O(1)。因此,总的时间复杂度为 O(n^2)。
空间复杂度:我们使用了一个长度为 n+1 的数组 c 和一个集合 seen,因此空间复杂度为 O(n)。
六、总结
通过使用双层循环和集合来遍历所有可能的子数组,并统计每个子数组的权值,我们可以高效地计算出每个权值的子数组数量。这种方法的时间复杂度为 O(n^2),空间复杂度为 O(n),适用于大多数情况。