力扣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;
    }
}
相关推荐
先吃饱再说9 小时前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰12 小时前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术13 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六16 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术17 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize17 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考1 天前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
To_OC2 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode