力扣滑动窗口总结java版(直接学模板+逐行注释解析!)

滑动窗口

一、定长滑动窗口

=================================================================================================

力扣1343.大小为K且平均值大于等于阈值的子数组数目(1317)

给你一个整数数组 arr 和两个整数 kthreshold

请你返回长度为 k 且平均值大于等于 threshold 的子数组数目。

https://leetcode.cn/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-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 - ki + k 范围( i - ki + k)内所有元素的平均值。如果在下标 i 前或后不足 k 个元素,那么 半径为 k 的子数组平均值-1

构建并返回一个长度为 n 的数组 avgs ,其中 avgs[i] 是以下标 i 为中心的子数组的 半径为 k 的子数组平均值

x 个元素的 平均值x 个元素相加之和除以 x ,此时使用截断式 整数除法 ,即需要去掉结果的小数部分。

  • 例如,四个元素 2315 的平均值是 (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(困难)

给你两个字符串 word1word2

如果一个字符串 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

word1word2 都只包含小写英文字母。

==============================================================================================

很明显需要维护各个字母的数目,我们定义一个长度为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 个不同的整数:12,以及 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;
    }
}
### 关于滑动窗口最大值问题的解法 对于给定的一个整数数组 `nums` 和一个大小为 `k` 的滑动窗口,目标是从左至右遍历该数组并记录每一个滑动窗口中的最大值。一种直观的方法是通过暴力求解来获取每个窗口的最大值,然而这种方法的时间复杂度较高,达到 O((n - k + 1) * k)[^1]。 为了提高效率,可以采用更优的数据结构——单调队列来进行处理。单调队列是一种特殊的双端队列,在这里用于存储当前窗口内元素下标的集合,并保持这些下标对应的数值按降序排列[^4]。具体来说: - 当新元素进入窗口时,移除队列中所有小于等于它的元素(因为它们不可能成为后续任何窗口的最大值),再将新的元素加入到队尾; - 如果队首元素已经不在当前窗口范围内,则将其弹出; - 此时队首即为当前窗口的最大值所在位置; 下面给出基于上述逻辑的具体 Python 实现方式: ```python from collections import deque def maxSlidingWindow(nums, k): result = [] window = deque() for i in range(len(nums)): # 移除不在当前窗口范围内的索引 if window and window[0] <= i - k: window.popleft() # 维护单调递减性质 while window and nums[i] >= nums[window[-1]]: window.pop() window.append(i) # 记录结果 if i >= k - 1: result.append(nums[window[0]]) return result ``` 此算法能够在线性时间内完成计算,时间复杂度降低到了 O(n),极大地提高了程序运行效率[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值