AI 刷题 148. 小A的子数组权重 题解 | 豆包MarsCode AI刷题

6 阅读5分钟

问题描述

小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 的数组,计算每个权值为 1n 的子数组的数量。其中,子数组的“权值”定义为该子数组内不同元素的个数。

我们需要输出一个长度为 n 的数组,其中第 i 个元素表示所有“权值”为 i 的子数组的数量。

解题思路

1. 子数组的定义和权值计算

  • 一个子数组是一个连续的数组片段。
  • 子数组的“权值”是指子数组中不同元素的个数。

例如,对于子数组 [1, 2, 2, 3],其中的不同元素有 1, 2, 3,所以该子数组的权值为 3

2. 暴力解法的局限性

我们可以通过暴力的方法,计算所有子数组的权值。对于每个子数组,计算其中不同元素的个数,统计每个权值的出现次数。然而,暴力解法会涉及到 O(n^2) 次子数组的遍历,并且每个子数组需要花费 O(n) 来计算其不同元素的个数,总体时间复杂度是 O(n^3),对于较大的输入来说是不可行的。

3. 优化思路

我们可以通过使用滑动窗口(双指针)来优化计算过程。滑动窗口的基本思想是:

  • 使用两个指针(leftright)来表示当前子数组的范围,left 固定时,移动 right 来扩展窗口。
  • 利用一个字典或者哈希表来记录当前窗口内每个元素的频次,进而计算当前窗口内的不同元素的个数。
  • 根据不同元素的个数更新对应的子数组权值的计数。

4. 具体步骤

  • 使用两个指针 leftright 表示当前的子数组范围,初始化时让 left = 0right = 0
  • 使用一个哈希表 freq_map 来记录当前窗口内各元素的频次。
  • 对于每个 right 指针的位置,计算当前窗口内不同元素的个数,并更新对应的计数。
  • 滑动 left 指针,确保窗口内不同元素的个数不超过当前考虑的权值。

5. 复杂度分析

  • 时间复杂度:使用滑动窗口方法,leftright 都只会遍历一次整个数组,因此时间复杂度为 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])

代码解释

  1. 初始化计数数组c 用来保存权值为 1 到 n 的子数组的数量。

  2. 滑动窗口的实现

    • 我们通过两个指针 leftright 遍历所有可能的子数组。
    • 使用哈希表 freq_map 来记录当前窗口内每个元素的频次。
    • distinct_count 记录当前窗口内不同元素的个数。
    • 每扩展一个子数组,就更新对应的 distinct_count 的计数。
  3. 返回结果:返回从 c[1]c[n] 的子数组数量,即权值从 1 到 n 的子数组数量。

时间和空间复杂度分析

  • 时间复杂度O(n^2),因为我们使用了两个嵌套循环,其中外层循环是 left 的遍历,内层循环是 right 的遍历,对于每对 leftright,我们更新哈希表的操作是 O(1)。因此,总的时间复杂度是 O(n^2)
  • 空间复杂度O(n),我们使用了一个哈希表来记录每个子数组的频次,最多需要存储 n 个元素的频次。

代码优化

目前的解法已经通过滑动窗口和哈希表将时间复杂度优化为 O(n^2),这是一个比较有效的解法。由于每个子数组的计算只在固定时间内完成,因此我们无法进一步显著优化时间复杂度,除非在极大数据量下通过更复杂的数据结构进行处理。

总结

  1. 解题思路:使用滑动窗口和哈希表来统计每个子数组的不同元素个数,并计算每个权值的子数组数量。
  2. 时间复杂度O(n^2),适合较大的 n
  3. 空间复杂度O(n),需要额外的空间存储哈希表。