好子数组的计数 | 豆包MarsCode AI刷题

97 阅读3分钟

题解

思路

这道题的核心在于找到所有子数组中恰好包含 ( k ) 个不同整数的子数组数。由于直接计算满足条件的子数组比较困难,题解采用了「滑动窗口法」结合「差分」来解决问题:

  1. 滑动窗口法的核心

    • 滑动窗口通过两个指针动态维护一个窗口,使得窗口中的子数组满足条件。
    • 对于「最多包含 ( k ) 个不同整数」的子数组,可以通过扩展窗口并统计所有符合条件的子数组数实现。
  2. 差分技巧

    • 子数组包含「恰好 ( k ) 个不同整数」的数量 = 子数组包含「最多 ( k ) 个不同整数」的数量 - 子数组包含「最多 ( k-1 ) 个不同整数」的数量。
    • 这种差分技巧有效地避免了重复统计,并将问题拆解为更简单的两个子问题。
  3. 实现细节

    • 用一个 count 哈希表记录每个整数在当前窗口中的频率,动态维护窗口中不同整数的数量。
    • 每次扩展窗口时,统计当前窗口内所有子数组数量,并在必要时收缩窗口。

代码详解

from collections import defaultdict

def solution(nums: list, k: int) -> int:
    def at_most(k):
        """计算最多包含 k 个不同整数的子数组数量"""
        count = defaultdict(int)  # 记录窗口中每个数字的出现次数
        left = 0  # 窗口左边界
        result = 0  # 结果累计
        
        for right in range(len(nums)):
            # 扩大窗口,添加当前右边界元素
            count[nums[right]] += 1
            
            # 如果不同整数超过 k,收缩窗口
            while len(count) > k:
                count[nums[left]] -= 1
                if count[nums[left]] == 0:
                    del count[nums[left]]  # 删除频率为 0 的数字
                left += 1
            
            # 窗口内符合条件的子数组数量
            result += right - left + 1
        
        return result
    
    # 恰好包含 k 个不同整数的子数组数量
    return at_most(k) - at_most(k - 1)

关键点总结

  1. 窗口动态维护

    • 扩展窗口时添加元素,收缩窗口时删除元素。
    • 窗口中不同整数的数量由 count 哈希表动态维护。
  2. 子数组统计

    • 每次扩展窗口时,新增的子数组数量为窗口长度 ( \text{right} - \text{left} + 1 )。
  3. 差分技巧

    • 差分计算让我们无需单独处理「恰好 ( k ) 个不同整数」的问题,转而解决更简单的「最多 ( k ) 个不同整数」的问题。

感受

  1. 滑动窗口法的优雅

    • 滑动窗口是处理子数组问题的经典方法,这道题通过「窗口+差分」的设计,清晰地将复杂问题分解为更易处理的子问题。
  2. 时间复杂度的控制

    • 滑动窗口的时间复杂度为 ( O(n) ),因为每个指针(左右边界)都至多遍历数组一次。
    • 对于这类子数组统计问题,避免 ( O(n^2) ) 的暴力枚举是滑动窗口法的巨大优势。
  3. 差分思路的巧妙性

    • 差分技巧让问题由复杂变简单,将「恰好包含 ( k ) 个不同整数」的问题拆解为两个「最多包含」问题,使得问题的解法非常自然流畅。
  4. 代码可读性高

    • 代码通过辅助函数 at_most(k) 的封装,使得逻辑清晰,易于扩展和维护。

总结

  • 本题非常适合用滑动窗口法解决,尤其是子数组的统计问题,滑动窗口往往能将暴力 ( O(n^2) ) 优化到 ( O(n) )。
  • 差分思路是解决「恰好包含 ( k ) 个」这类问题的常见技巧,非常值得学习和掌握。
  • 在实现过程中,通过哈希表维护窗口状态是一个常见而有效的手段。

这道题让我对滑动窗口法在子数组问题上的应用有了更深刻的理解,同时也认识到差分技巧在复杂问题中的简化作用,是一道非常经典且值得反复练习的题目。