【每日三题】滑动窗口刷题模板

87 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情 >>

每日三刷,剑指千题

计划简介:

  • 每日三题,以中等题为主,简单题为辅进行搭配。保证质量题1道,数量题3道。
  • 每日早通勤在LeetCode手机端选题,思考思路,没答案的直接看题解。
  • 每日中午进行编码,时间控制在一小时之内。
  • 下班前半小时进行整理总结,并发布到掘金每日更文活动。

说明:

  • 基于以前的刷题基础,本次计划以中等题为主,大部分中等题都可以拆分为多个简单题,所以数量保证3,质量保证一道中等题即可。
  • 刷题顺序按照先刷链表、二叉树、栈、堆、队列等基本数据结构,再刷递归、二分法、排序、双指针等基础算法,最后是动态规划、贪心、回溯、搜索等复杂算法。
  • 刷题过程中整理相似题型,刷题模板。
  • 目前进度 139/1000

[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 的子数组。

解析

滑动窗口经典题,附滑动窗口模板:

滑动窗口 + 变量计数模板:

class Solution {
    public int slidingWindow(int[] nums, int k) {
        //数组/字符串长度
        int n = nums.length;
        //双指针,表示当前遍历的区间[left, right],闭区间
        int left = 0, right = 0;
        //定义变量统计 子数组/子区间 是否有效
        int sum = 0;
        //定义变量动态保存最大 求和/计数
        int res = 0;
​
        //右指针遍历到数组尾
        while (right < n) {
            //增加当前右指针对应的数值
            sum += nums[right];
            //当在该区间内 sum 超出定义范围
            while (sum > k) {
                //先将左指针指向的数值减去
                sum -= nums[left];
                //左指针右移
                left++;
            }
            //到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
            res = Math.max(res, right - left + 1);
            //移动右指针,去探索下一个区间
            right++;
        }
        return res;
    }
}

滑动窗口 + 哈希表存储模板:

class Solution {
    public String slidingWindow(String s, String t) {
        //创建两个哈希表,分别记录 [需要的][加入的]
        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> map = new HashMap<>();
​
        //创建 [双指针][有效数量]
        int left = 0, right = 0;
        int valid = 0;
​
        //外层循环,供右指针遍历
        while(right < s.length()){
            //创建临时 c 字符,是移入 窗口 内的字符
            char c = s.charAt(right);
            
            //进行窗口一系列逻辑更新
            ...
            
            //判断左指针是否要右移即窗口收缩:有效数量足够满足条件
             /*  可能是规定的窗口大小超出了,可能是有效值数量达成了
             1.  while(valid == need.size())
             2.  while(right - left + 1 >= s1.length())      
             */           
            while(windows need shrink){
                // 创建 d 是要移除窗口的字符
                char d = s.charAt(left);
                left++;
​
                //进行窗口一系列逻辑更新
                ...
            }
            
            //右指针右移
            right++;
        }
    }
}

Code

class Solution {
        public int numSubarrayProductLessThanK(int[] nums, int k) {
            if (k <= 1) {
                return 0;
            }
            int n = nums.length;
            int left = 0, right = 0;
            int ans = 0;
            int temp = 1;
​
            while (right < n) {
                temp *= nums[right];
                while (temp >= k) {
                    // 重构窗口
                    temp /= nums[left];
                    left++;
                }
                // 计算该窗口内以left开头的子数组有多少
                ans += right - left + 1;
                right++;   // 扩大窗口
            }
            return ans;
        }
    }

[1984]学生分数的最小差值

给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k

从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分最低分差值 达到 最小化

返回可能的 最小差值

示例 1:

输入:nums = [90], k = 1
输出:0
解释:选出 1 名学生的分数,仅有 1 种方法:
- [90] 最高分和最低分之间的差值是 90 - 90 = 0
可能的最小差值是 0

解析

关键得出排序后结果一定是连续的,不连续就无法用滑动窗口。

Code

class Solution {
        public int minimumDifference(int[] nums, int k) {
            Arrays.sort(nums);
            int ans = nums[k - 1] - nums[0];
            for (int i = k; i < nums.length; i++) {
                ans = Math.min(nums[i] - nums[i - k + 1], ans);
            }
            return ans;
        }
    }

[219]存在重复元素 II

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 ij ,满足 nums[i] == nums[j]abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false

示例 1:

输入:nums = [1,2,3,1], k = 3
输出:true

解析

套需要记录模板。

Code

class Solution {
        public boolean containsNearbyDuplicate(int[] nums, int k) {
            // 窗口最大长度为 k
            int n = nums.length;
            int left = 0, right = 0;
            Set<Integer> set = new HashSet<>();
            while (right < n) {
                while (right - left > k) {
                    set.remove(nums[left]);
                    left++;
                }
                if (!set.add(nums[right])) {
                    return true;
                }
                right++;
            }
            return false;
        }
    }

\