力扣992做题笔记

左神做法的理论依据

我们可以通过 集合的包含关系具体示例枚举 来直观理解这一推导过程。以下结合题目示例 1 进行详细说明:

示例 1 分析

输入nums = [1,2,1,2,3], k = 2
目标 :计算恰好包含 2 种不同整数 的子数组个数。

步骤一集合 A 的计数

滑动窗口的核心逻辑是:

  • 对于每个右边界 r,找到最小左边界 l,使得窗口 [l, r] 内不同整数个数 ≤k。
  • 此时,左边界可以是 [l, r] 中的任意位置,因此贡献 r - l + 1 个子数组。

以示例 1 为例,滑动窗口计算 findNumsofMost(nums, 2)(即 |A|)的过程如下:

r 元素 窗口内元素 不同数 最小左边界 l 贡献子数组数
0 1 [1] 1 0 1
1 2 [1,2] 2 0 2
2 1 [1,2,1] 2 0 3
3 2 [1,2,1,2] 2 0 4
4 3 [2,1,2,3] 3(超过 2) 移动 l 到 1 窗口 [1,4] 含 2,1,3(3 种,仍超过)→ 继续移动 l 到 2 → 窗口 [2,4] 含 1,2,3(3 种,仍超过)→ 移动 l 到 3 → 窗口 [3,4] 含 2,3(2 种)→ 最小 l=3,贡献 (4-3+1=2)

累加结果 :(1+2+3+4+2=12),即 |A|=12。

findNumsofMost(nums, 1)(即 |B|)的计算结果为 5(仅含单个元素的子数组),因此:

步骤 2:定义集合 B(不超过 k-1 种)

集合 B 包含所有不同整数个数 ≤1 的子数组(即仅含 1 种整数)。

枚举所有满足条件的子数组:

  • 单个元素:[1], [2], [1], [2], [3],共 5 个。
  • 连续相同元素的子数组:
    • [1](位置 0)、[2](位置 1)、[1](位置 2)、[2](位置 3)、[3](位置 4)。
    • 无长度 ≥2 的连续相同元素(因为数组中相邻元素不同)。
  • 集合 B 的总数:5 个。
步骤 3:计算集合差集 A - B

根据定义:
恰好含 k 种的子数组个数 = |A| - |B|

  • 集合 A 包含 含 1 种或 2 种 的子数组。

  • 集合 B 包含 仅含 1 种 的子数组。

  • 因此,A - B 即为仅含 2 种的子数组 ,其数量为:

    |A| - |B| = 12 - 5 = 7

    这与题目示例 1 的输出一致。

数学推导:集合差集的严格性

  • 设 (f(m)) 表示 不同整数个数 ≤m 的子数组个数。

  • 恰好含 k 种的子数组必须满足:

    • 不同整数个数 ≤k(属于集合 A),
    • 且不同整数个数 >k-1(不属于集合 B)。
  • 因此,恰好含 k 种的子数组是 A 中排除 B 的部分,即:

    = f(k) - f(k-1)

例子 2:更简单的数组(nums = [1,1,2], k=2)

为了更直观,我们用一个更短的数组验证。

问题目标

找到所有「恰好有 2 种不同整数」的子数组。

数组分析

数组 [1,1,2] 的所有子数组及其不同整数个数:

子数组 内容 不同整数个数
[0,0] [1] 1
[0,1] [1,1] 1
[0,2] [1,1,2] 2
[1,1] [1] 1
[1,2] [1,2] 2
[2,2] [2] 1
集合 A(≤2)和 B(≤1)的大小
  • 集合 A(≤2):所有子数组都满足(因为最大不同个数是 2),共 6 个。
  • 集合 B(≤1) :不同整数个数为 1 的子数组,即 [0,0], [0,1], [1,1], [2,2] → 共 4 个。
结论验证

恰好有 2 种不同整数的子数组是 [0,2], [1,2] → 共 2 个。

根据公式 |A| - |B| = 6 - 4 = 2,与实际结果一致。

数学推导:为什么差集是正确的?

用数学符号形式化定义:

  • f(k) 为「不同整数个数 ≤k 的子数组数目」。
  • g(k) 为「不同整数个数恰好等于k 的子数组数目」。

根据定义,f(k) 是所有 g(1), g(2), ..., g(k) 的和,即:

f(k) = g(1) + g(2) + ... + g(k)

同理,f(k-1) 是:

f(k-1) = g(1) + g(2) + ... + g(k-1)

两式相减得:

f(k) - f(k-1) = g(k)

这正是题目要求的「恰好有k种不同整数的子数组数目」。

总结

通过具体例子和数学推导可以看出:
「不超过k的子数组数」减去「不超过k-1的子数组数」,本质是通过「前缀和相减」的方式,精准提取出「恰好等于k」的子数组数目。这种方法将复杂的「恰好k」问题转化为更易计算的「不超过k」问题,是滑动窗口算法中常用的技巧。

代码

java 复制代码
class Solution {
    public int subarraysWithKDistinct(int[] nums, int k) {
        return findNumsofMost(nums, k) - findNumsofMost(nums, k - 1); 
    }
    private static final int maxn = 20001;
    private static int[] cnts = new int[maxn];
    private int findNumsofMost(int[] nums, int k) { 
        Arrays.fill(cnts, 1, nums.length + 1, 0);
        int ans = 0;
        for (int r = 0, l = 0, collect = 0; r < nums.length; r ++) { 
            if (++cnts[nums[r]] == 1) { 
                collect ++;
            }
            while (collect > k) { 
                if (--cnts[nums[l++]] == 0) { 
                    collect--;
                }
            }
            ans += r - l + 1;
        }
        return ans;
    }
}
相关推荐
codists1 小时前
《算法导论(第4版)》阅读笔记:p86-p90
算法
ShineSpark1 小时前
C++面试3——const关键字的核心概念、典型场景和易错陷阱
c++·算法·面试
智驱力人工智能4 小时前
AI移动监测:仓储环境安全的“全天候守护者”
人工智能·算法·安全·边缘计算·行为识别·移动监测·动物检测
编程绿豆侠6 小时前
力扣HOT100之二叉树:199. 二叉树的右视图
算法·leetcode·职场和发展
飞川撸码6 小时前
【LeetCode 热题100】17:电话号码的字母组合(详细解析)(Go语言版)
算法·leetcode·golang·dfs
蒟蒻小袁6 小时前
力扣面试150题--从前序与中序遍历序列构造二叉树
算法·leetcode·面试
闭月之泪舞7 小时前
初识函数------了解函数的定义、函数的参数、函数的返回值、说明文档的书写、函数的嵌套使用、变量的作用域(全局变量与局部变量)
python·算法·机器学习
GUIQU.8 小时前
【每日一题丨2025年5.12~5.18】排序相关题
算法·排序·每日一题
哪 吒8 小时前
2025B卷 - 华为OD机试七日集训第2期 - 按算法分类,由易到难,循序渐进,玩转OD(Python/JS/C/C++)
python·算法·华为od·华为od机试·2025b卷