滑动窗口
一、定长滑动窗口
=================================================================================================
力扣1343.大小为K且平均值大于等于阈值的子数组数目(1317)
给你一个整数数组 arr
和两个整数 k
和 threshold
。
请你返回长度为 k
且平均值大于等于 threshold
的子数组数目。
=================================================================================================
class Solution {
public int numOfSubarrays(int[] arr, int k, int threshold) {
double sum = 0;
int count = 0;
for (int i = 0; i < k; i++) {
sum += arr[i];
}
if (sum >= threshold * k) {
count++;
}
for (int i = 1, j = k; j < arr.length; j++, i++) {
sum = sum + arr[j] - arr[i - 1];
if (sum >= threshold * k) {
count++;
}
}
return count;
}
}
模板:
class Solution {
public int numOfSubarrays(int[] arr, int k, int threshold) {
// 窗口长度为k
//先求第一个窗口 i=0;i<k;i++
for (int i = 0; i < k; i++) {
.......;
}
//判断第一个窗口是否满足条件
if (sum >= threshold * k) {
count++;
}
// 开始窗口滑动 (i=1,j=k;j<arr.length;i++,j++)
for (int i = 1, j = k; j < arr.length; j++, i++) {
// 每次移除最前面的arr[i-1],加入最后面的arr[j];
sum = sum + arr[j] - arr[i - 1];
// 滑动后再次判断是否满足条件
if (sum >= threshold * k) {
count++;
}
}
return count;
}
}
=================================================================================================
例子:2090.半径为k的子数组平均值(1358)
https://leetcode.cn/problems/k-radius-subarray-averages
给你一个下标从 0 开始的数组 nums
,数组中有 n
个整数,另给你一个整数 k
。
半径为 k 的子数组平均值 是指:nums
中一个以下标 i
为 中心 且 半径 为 k
的子数组中所有元素的平均值,即下标在 i - k
和 i + k
范围(含 i - k
和 i + k
)内所有元素的平均值。如果在下标 i
前或后不足 k
个元素,那么 半径为 k 的子数组平均值 是 -1
。
构建并返回一个长度为 n
的数组 avgs
,其中 avgs[i]
是以下标 i
为中心的子数组的 半径为 k 的子数组平均值 。
x
个元素的 平均值 是 x
个元素相加之和除以 x
,此时使用截断式 整数除法 ,即需要去掉结果的小数部分。
-
例如,四个元素
2
、3
、1
和5
的平均值是(2 + 3 + 1 + 5) / 4 = 11 / 4 = 2.75
,截断后得到2
==============================================================================================
按照模板解题:
class Solution {
public int[] getAverages(int[] nums, int k) {
int n = nums.length;
// 将输出存入结果数组
int[] avgs = new int[n];
// long防止溢出
long sum = 0;
// 根据题目要求将结果数组全部初始化为-1
Arrays.fill(avgs, -1);
// 如果不存在半径为k的数组就返回全为-1的avgs
if (2 * k + 1 > n) {
return avgs;
}
// 开始套模板
// 判断第一个窗口,滑动窗口长度为2*k+1
for (int i = 0; i < 2 * k + 1; i++) {
sum += nums[i];
}
// 将第一个窗口结果存入
avgs[k] = (int) (sum / (2 * k + 1));
// 将窗口滑动,j等于窗口长度,i等于1
for (int j = 2 * k + 1, i = 1; j < n; j++, i++) {
// 每次移除最前面的arr[i-1],加入最后面的arr[j];
sum = sum + nums[j] - nums[i - 1];
// 将滑动后的结果存入
avgs[k + i] = (int) (sum / (2 * k + 1));
}
return avgs;
}
}
二、不定长滑动窗口
不定长滑动窗口主要分为三类:求最长子数组,求最短子数组,以及求子数组个数。
2.1、求最长/最大
一般题目都有「至多」的要求。一般碰到元素种类都要与哈希表一起使用
=================================================================================================
力扣3.无重复字符的最长子串(中等)
https://leetcode.cn/problems/longest-substring-without-repeating-characters
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
=================================================================================================
解答如下:
class Solution {
public int lengthOfLongestSubstring(String s) {
int[] hs = new int[128];
int max = 0;
for (int i = 0, j = 0; j < s.length(); j++) {
hs[s.charAt(j)]++;
while (hs[s.charAt(j)] > 1) {
hs[s.charAt(i)]--;
i++;
}
max = Math.max(max, j - i + 1);
}
return max;
}
}
模板:
class Solution {
public int lengthOfLongestSubstring(String s) {
// 判断重复,所用用数组哈希表
int[] hs = new int[128];
// 找最长,初始化为0;找最小,初始化为Integer.MAX_VALUE
int max = 0;
// 窗口模板:(i = 0, j = 0; j < s.length(); j++)
// 注意:在for循环内只拓展右边界,让更多元素进来,满足题目要求后收缩左边界。
for (int i = 0, j = 0; j < s.length(); j++) {
// 将右边界放进来的元素个数进行计数,char直接转成ASCII码
hs[s.charAt(j)]++;
// 因为不含重复字符,所以当放进来的元素已经在窗口内存在的时候,收缩左边界
// 收缩左边界,此时有两种情况:
// 1.左边界出去的元素和右边界进来的元素不一致,则继续收缩左边界
// 2.左边界出去的元素正好是右边界进来的元素,右边界元素个数降为1,退出while循环,继续j++拓展右边界
while (hs[s.charAt(j)] > 1) {
// 将左边界元素个数减一
hs[s.charAt(i)]--;
// 收缩左边界
i++;
}
// 每次都要对满足条件(退出while)的窗口的长度进行取较大值操作
// 窗口长度为j-i+1
max = Math.max(max, j - i + 1);
}
return max;
}
}
核心就是拓展右边界,不满足要求了就收缩左边界直至满足要求,然后更新答案。
=================================================================================================
例子:3090.每个字符最多出现两次的最长子字符串(1329)
https://leetcode.cn/problems/maximum-length-substring-with-two-occurrences
给你一个字符串 s
,请找出满足每个字符最多出现两次的最长子字符串,并返回该子字符串的 最大 长度。
示例 1:
输入: s = “bcbbbcba”
输出: 4
**解释:**以下子字符串长度为 4,并且每个字符最多出现两次:"bcbbbcba"
。
示例 2:
输入: s = “aaaa”
输出: 2
**解释:**以下子字符串长度为 2,并且每个字符最多出现两次:"aaaa"
。
================================================================================================
根据模板解题:
我们先来找核心:拓展右边界,直至不满足要求。
要求是什么?要求是每个字符出现个数大于2
所以答案就出来了
class Solution {
public int maximumLengthSubstring(String s) {
// 求最大初始化为0
int max = 0;
// 定义哈希数组
int[] hs = new int[128];
// 模板:(i = 0, j = 0; j < s.length(); j++)
for (int i = 0, j = 0; j < s.length(); j++) {
// 放进来的元素个数加1
hs[s.charAt(j)]++;
// 不满足要求就收缩左边界
while (hs[s.charAt(j)] > 2) {
// 左边出去的元素个数减1
hs[s.charAt(i)]--;
// 收缩左边界
// 注意:一定先减1再移动窗口 先减再出 不然i指向就错误了
i++;
}
// 每次满足要求就更新
max = Math.max(max, j - i + 1);
}
return max;
}
}
=================================================================================================
例子2:904.水果成篮(1516)
https://leetcode.cn/problems/fruit-into-baskets
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits
表示,其中 fruits[i]
是第 i
棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits
,返回你可以收集的水果的 最大 数目。
示例 1:
输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。
示例 2:
输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。
示例 3:
输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。
示例 4:
输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。
=================================================================================================
阅读理解题,其实fruits[i]表示的是水果种类
题目翻译成人话就是 :找至多包含两种元素的最长子串,返回其长度
核心是找要求,要求是什么?
要求是至多包含两种元素。
class Solution {
public int totalFruit(int[] fruits) {
// 定义哈希数组来记录每种水果出现的个数,数组值大于0即表示水果出现,我们就只需要统计大于0的数组值个数
int[] hs = new int[fruits.length];
// 求最大初始化为0
int max = 0;
// 用来统计大于0数组值的个数(水果出现种数)
int cnt = 0;
// 模板:(int i = 0, j = 0; j < fruits.length; j++)
for (int i = 0, j = 0; j < fruits.length; j++) {
// 如果加进来的[j]元素个数为0,则说明是新水果,我们将cnt加1
if (hs[fruits[j]] == 0) {
cnt++;
}
// 更新[j]元素个数为1
hs[fruits[j]]++;
// 如果cnt>2 也就是窗口内元素种类多于2,不满足要求
while (cnt > 2) {
// 将移出的元素[i]的个数减1
hs[fruits[i]]--;
// 如果减到0了就将cnt减1,说明这种水果个数为0,即不存在,所以水果种类减1
if (hs[fruits[i]] == 0) {
cnt--;
}
// 收缩左边界
i++;
}
//更新结果,长度为j-i+1
max = Math.max(max, j - i + 1);
}
return max;
}
}
=================================================================================================
例子3:1658.将x减到0的最小操作数(1817)
https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero
给你一个整数数组 nums
和一个整数 x
。每一次操作时,你应当移除数组 nums
最左边或最右边的元素,然后从 x
中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
如果可以将 x
恰好 减到 0
,返回 最小操作数 ;否则,返回 -1
。
示例 1:
输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。
示例 2:
输入:nums = [5,6,7,8,9], x = 4
输出:-1
示例 3:
输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。
=================================================================================================
这个题我们只需要换一个想法,看示例1,用x减去最后的2,3可以得到结果,其实也可以把前面的[1,1,4]看成一个滑动窗口,这个滑动窗口的和为数组nums全部元素的和减x的值。最小操作次数换句话说其实就是剩下的元素最多,即求满足条件滑动窗口的长度最大值。
class Solution {
public int minOperations(int[] nums, int x) {
// 求最大值初试化为0
int max = 0;
int ans = -1;
// 初始化数组和为0
int sum = 0;
int n = nums.length;
// 循环求数组和
for (int i = 0; i < n; i++) {
sum += nums[i];
}
// 如果数组和都没x大,很明显x不可能减到0,返回-1
if (x > sum) {
return -1;
}
// 将sum-x作为我们的目标值t
int t = sum - x;
// 重复利用变量sum,初始化为0,现在的sum用来求滑动窗口内元素的和
sum = 0;
// 模板:(int i = 0, j = 0; j < n; j++)
for (int i = 0, j = 0; j < n; j++) {
// 模板:加上右边进来元素的值
sum += nums[j];
// 当sum>t的时候不满足要求,我们要找的是sum==t,此时收缩左边界可以减小sum的值
while (sum > t) {
// 减去左边出去元素的值
sum -= nums[i];
// 收缩左边界
i++;
}
// 此时有两种情况:1.sum<t 2.sum==t 我们需要的是sum==t,如果sum<t,则for循环拓展右边界(j++)
// 如果sum==t,满足要求,更新结果
if (sum == t) {
max = Math.max(max, j - i + 1);
// 不要忘了我们的结果是n-max;
ans = n - max;
}
}
return ans;
}
}
本题难度分为1817,主要体现在转换思维。
本题的要求是使sum与目标值t相等,如果sum<t则拓展右边界放进元素增大sum的值,反之如果sum>t,则收缩左边界移出元素缩小 sum的值。
2.2 求最短/最小
一般题目都有「至少」的要求。
============================================================================================
力扣209.长度最小的子数组(中等)
https://leetcode.cn/problems/minimum-size-subarray-sum
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的
子数组
[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
解答如下:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
// 之前说过,求最小要初始化为Integer.MAX_VALUE
int result = Integer.MAX_VALUE;
int sum = 0;
// 模板:(int i = 0, j = 0; j < nums.length; j++)
for (int i = 0, j = 0; j < nums.length; j++) {
// 加上拓展右边界放进来的[j]元素
sum += nums[j];
// 核心:找要求:此时变成找满足要求的!!!!
// 满足要求我们就收缩左边界看看,因为要找最短的,如果还满足要求就找到了一个更短的
while (sum >= target) {
// 满足要求先更新结果,取最小值
result = Math.min(result, j - i + 1);
// 把移除的[i]元素减去
sum -= nums[i];
// 收缩左边界
i++;
// 此时若还满足要求继续while 不满足要求则退出while,执行for的j++拓展右边界
}
}
// 求最小的一般都要进行这个判断,如果结果从未被更新过(一直是Integer.MAX_VALUE,则根据题目要求返回0)
if (result != Integer.MAX_VALUE) {
return result;
} else {
return 0;
}
// 也可以写成一句模板:return result==Integer.MAX_VALUE ? 0 : result;
}
}
=================================================================================================
例子:2875.无限数组的最短子数组(1914)
给你一个下标从 0 开始的数组 nums
和一个整数 target
。
下标从 0 开始的数组 infinite_nums
是通过无限地将 nums 的元素追加到自己之后生成的。
请你从 infinite_nums
中找出满足 元素和 等于 target
的 最短 子数组,并返回该子数组的长度。如果不存在满足条件的子数组,返回 -1
。
示例 1:
输入:nums = [1,2,3], target = 5
输出:2
解释:在这个例子中 infinite_nums = [1,2,3,1,2,3,1,2,...] 。
区间 [1,2] 内的子数组的元素和等于 target = 5 ,且长度 length = 2 。
可以证明,当元素和等于目标值 target = 5 时,2 是子数组的最短长度。
示例 2:
输入:nums = [1,1,1,2,3], target = 4
输出:2
解释:在这个例子中 infinite_nums = [1,1,1,2,3,1,1,1,2,3,1,1,...].
区间 [4,5] 内的子数组的元素和等于 target = 4 ,且长度 length = 2 。
可以证明,当元素和等于目标值 target = 4 时,2 是子数组的最短长度。
示例 3:
输入:nums = [2,4,6,8], target = 3
输出:-1
解释:在这个例子中 infinite_nums = [2,4,6,8,2,4,6,8,...] 。
可以证明,不存在元素和等于目标值 target = 3 的子数组。
=================================================================================================
此题重点是无限追加,其实不难发现,我们只需要讨论一次追加即可,之后的每次追加效果都相同,只需要记录原数组的和total,之后用target去模total即可。
追加我们可以采用多种方式,这里我们直接新开一个数组,将它的元素设置为追加后的元素。
class Solution {
public int minSizeSubarray(int[] nums, int target) {
int n = nums.length;
// 新数组的长度一定为原数组的两倍,因为追加了一次,相当于两个nums数组首尾相连
int[] newNums = new int[2 * n];
// 用long存和防止溢出
long total = 0;
// 计算原数组的和total
for (int x : nums)
total += x;
// 将两个原数组首尾相连
for (int i = 0; i < n; i++) {
newNums[i] = nums[i];
newNums[i + n] = nums[i];
}
// 求最小初始化为Integer.MAX_VALUE
int len = Integer.MAX_VALUE;
int sum = 0;
// 模板:(int i = 0, j = 0; j < 2 * n; j++)
for (int i = 0, j = 0; j < 2 * n; j++) {
// 加上加入进来的[j]元素
sum += newNums[j];
// 如果不满足要求。(要求是等于taget)就收缩左边界
while (sum > target % total) {
sum -= newNums[i];
i++;
}
// 此时判断是否相等,不相等就执行for循环的j++来增大sum的值
// 相等就更新答案
if (sum == target % total) {
len = Math.min(len, j - i + 1);
}
}
// 模板 返回
return len == Integer.MAX_VALUE ? -1 : len + (int) (target / total) * n;
}
}
一个很高分的中等题也可以直接用模板秒杀,只是需要注意可以将无限追加转换成追加一次求模。
2.3、求子数组个数
2.3.1、越长越合法
一般要写 ans += left
=================================================================================================
力扣1358.包含所有三种字符的子字符串数目(1646)
给你一个字符串 s
,它只包含三种字符 a, b 和 c 。
请你返回 a,b 和 c 都 至少 出现过一次的子字符串数目。
示例 1:
输入:s = "abcabc"
输出:10
解释:包含 a,b 和 c 各至少一次的子字符串为 "abc", "abca", "abcab", "abcabc", "bca", "bcab", "bcabc", "cab", "cabc" 和 "abc" (相同字符串算多次)。
示例 2:
输入:s = "aaacb"
输出:3
解释:包含 a,b 和 c 各至少一次的子字符串为 "aaacb", "aacb" 和 "acb" 。
示例 3:
输入:s = "abc"
输出:1
=================================================================================================
这个题目要统计字符a、b、c出现的个数,所以我们可以用哈希数组来维护。
class Solution {
public int numberOfSubstrings(String s) {
// 定义一个长度为3的整型数组,对应关系入下
int[] hs = new int[3];
// a 0
// b 1
// c 2
// 将字符串转为字符数组(可转可不转)
char[] ss = s.toCharArray();
int n = ss.length;
int sum = 0;
// 模板:(int i = 0, j = 0; j < n; j++)
for (int i = 0, j = 0; j < n; j++) {
// 将加入进来的[j]元素数量加1
hs[ss[j] - 'a']++;
// 判断是否满足要求,满足要求后我们就将左边界收缩
// 求数目的这种题一般就是求最短的满足条件
while (hs[0] >= 1 && hs[1] >= 1 && hs[2] >= 1) {
// 模板一:在while循环加上n-j,n是数组长度,j是右指针
sum += n - j;
hs[ss[i] - 'a']--;
i++;
}
// 模板二:在while外加上 sum+=i;
//sum+=i;
// 二选一即可
}
return sum;
}
}
=================================================================================================
例子:3298.统计重新排列会后包含另一个字符串的子字符串数目II(困难)
给你两个字符串 word1
和 word2
。
如果一个字符串 x
重新排列后,word2
是重排字符串的
前缀,那么我们称字符串 x
是 合法的 。
请你返回 word1
中 合法 子字符串的数目。
注意 ,这个问题中的内存限制比其他题目要 小 ,所以你 必须 实现一个线性复杂度的解法。
示例 1:
**输入:**word1 = “bcca”, word2 = “abc”
**输出:**1
解释:
唯一合法的子字符串是 "bcca"
,可以重新排列得到 "abcc"
,"abc"
是它的前缀。
示例 2:
**输入:**word1 = “abcabc”, word2 = “abc”
**输出:**10
解释:
除了长度为 1 和 2 的所有子字符串都是合法的。
示例 3:
**输入:**word1 = “abcabc”, word2 = “aaabc”
**输出:**0
word1
和 word2
都只包含小写英文字母。
==============================================================================================
很明显需要维护各个字母的数目,我们定义一个长度为26的数组hs[26]作为哈希数组,hs[0]代表’a’,以此类推。
我们只需要满足word1里包含word2的所有字母即可。
我们先统计word2中每个字母的个数(用欠债方法表示,所以用负值存,比如hs[0]==-2就代表word1里需要有2个字符a)
然后套模板去滑动窗口,如果发现word1单词的哈希数组值是负的,就说明是我们所需要的,我们的“欠债”就减一,“欠债”为debt,初始化为word2.length()。所以我们的要求就是让debt==0
class Solution {
public long validSubstringCount(String word1, String word2) {
// 定义长度为26的哈希数组来记录字母的个数
int[] hs = new int[26];
long ans = 0;
// 先统计word2中各字母的个数,也就是word1应该包含的字母个数
// 用欠债方法表示,所以用负值存,比如hs[0]==-2就代表word1里需要有2个字符a)
for (int i = 0; i < word2.length(); i++) {
hs[word2.charAt(i) - 'a']--;
}
// 模板(int i = 0, j = 0, debt = word2.length(); j < word1.length(); j++)
// 初始化欠债debt为word2.length(),后续每满足一个我们就减一,我们的要求是debt==0
for (int i = 0, j = 0, debt = word2.length(); j < word1.length(); j++) {
// 加进来的元素[i]个数小于0说明是我们需要的,所以debt-1
if (hs[word1.charAt(j) - 'a'] < 0) {
debt--;
}
// 把[i]元素个数加1
hs[word1.charAt(j) - 'a']++;
// 满足要求的时候debt==0
while (debt == 0) {
// 模板一
ans += (word1.length() - j);
// 我们尝试把左边的[i]元素移出
hs[word1.charAt(i) - 'a']--;
// 如果移出去的元素个数在移出后变为负的,就说明我们移出去了一个word2中的元素
if (hs[word1.charAt(i) - 'a'] < 0) {
// 此时欠债不满足debt==0,退出while并且debt++;
debt++;
}
// 若移出元素个数不小于0,则说明移出的元素无关紧要,不是word2中的元素
// 收缩左窗口
i++;
}
// 退出while后扩大右窗口继续判断。
// 模板二:ans+=i;
}
return ans;
}
}
2.3.2、越短越合法
一般要写 ans += right - left + 1
=================================================================================================
力扣713.乘积小于k的子数组(中等)
给你一个整数数组 nums
和一个整数 k
,请你返回子数组内所有元素的乘积严格小于 k
的连续子数组的数目。
示例 1:
输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2]、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
示例 2:
输入:nums = [1,2,3], k = 0
输出:0
1 <= nums[i] <= 1000
=================================================================================================
套模板就行
看示例1:当[10,5]满足的时候短的[10]和[5]肯定也满足,这就叫越短越合法。此时套模板ans+=j-i+1;
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
int n = nums.length;
int sum = 1;
int count = 0;
// 因为nums[i]>=1,所以当k<=1时不存在严格小于k的乘积,返回0
if (k <= 1) {
return 0;
}
// 套模板
for (int i = 0, j = 0; j < n; j++) {
sum *= nums[j];
// 不满足要求就去收缩左边界
while (sum >= k) {
sum = sum / nums[i];
i++;
}
// 模板!什么时候用这个看上面
count = count + j - i + 1;
}
return count;
}
}
2.4.2恰好型滑动窗口
例如,要计算有多少个元素和恰好等于 kk 的子数组,可以把问题变成:
- 计算有多少个元素和 ≥k 的子数组。
- 计算有多少个元素和 >k,也就是 ≥k+1 的子数组。
=================================================================================================
力扣992.K个不同整数的子数组(2210)
给定一个正整数数组 nums
和一个整数 k
,返回 nums
中 「好子数组」 的数目。
如果 nums
的某个子数组中不同整数的个数恰好为 k
,则称 nums
的这个连续、不一定不同的子数组为 「****好子数组 」。
- 例如,
[1,2,3,1,2]
中有3
个不同的整数:1
,2
,以及3
。
子数组 是数组的 连续 部分。
示例 1:
输入:nums = [1,2,1,2,3], k = 2
输出:7
解释:恰好由 2 个不同整数组成的子数组:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].
示例 2:
输入:nums = [1,2,1,3,4], k = 3
输出:3
解释:恰好由 3 个不同整数组成的子数组:[1,2,1,3], [2,1,3], [1,3,4].
=================================================================================================
越长越合法的结合,要求恰好为k,那我们直接用>=k+1-<=k
因为要统计整数个数,所以用哈希表来维护个数
class Solution {
// 模板,直接k-k+1
public int subarraysWithKDistinct(int[] nums, int k) {
return hd(nums, k) - hd(nums, k + 1);
}
// 自定义函数hd来实现要求
public int hd(int[] nums, int k) {
// 定义一个哈希表
Map<Integer, Integer> map = new HashMap();
int ans = 0;
// 直接套模板
for (int i = 0, j = 0; j < nums.length; j++) {
// 将右边加进来的元素[j]存到哈希表,哈希表里如果已经有了该元素,就把该元素对应的值(个数)+1,否则将值设为默认值0
map.put(nums[j], map.getOrDefault(nums[j], 0) + 1);
// map的size()其实就是不同元素的个数,因为哈希表不重复
// 当满足要求的时候尝试移出左边界的元素[i]
while (map.size() >= k) {
// 将哈希表的[i]元素的值(个数)减1
map.put(nums[i], map.get(nums[i]) - 1);
// 如果个数减到0了就移出该元素,此时size-1
if (map.get(nums[i]) == 0) {
map.remove(nums[i]);
}
i++;
}
// 越长越合法模板
ans += i;
}
return ans;
}
}