ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

滑动窗口算法

2022-06-25 20:00:36  阅读:160  来源: 互联网

标签:right 窗口 nums int ++ 算法 数组 滑动 left


滑动窗口算法

名称

滑动窗口算法

场景

当遇到找出序列中连续子序列满足某些条件的问题的时候,可以使用滑动窗口. 序列包括:数组,字符串,List,链表等.
当我们遇到了求满足目标的连续子序列的时候,第一直觉适用滑动子窗口

解决方案

滑动窗口模板:

//创建双指针
int left= start;
int right= start;

//右指针移动
while(right < end){

 将rank=right位置的数据添加到窗口中.
 
 while(!窗口不合规){
  // 判断是否满足条件,更新结果.
    left++;
 }
 
 if(窗口满足最终条件){
   更新最终结果.
   刷新窗口.
 }
 right++;
  
}

返回结果.


效果

满足某个要求的连续子串普通的实现至少是 O(n^2).但是适用滑动窗口能够将问题复杂度降低到 O(n).

实例

问题一 剑指 Offer II 008. 和大于等于 target 的最短子数组

给定一个含有n个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
给定一个含有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

提示:
1 <= target <= 109

1 <= nums.length <= 105

1 <= nums[i] <= 105

进阶:

如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

class Solution {
       public int minSubArrayLen(int target, int[] nums) {

        /**
         * 7
         * [2,3,1,2,4,3]
         *
         * 给定一个数组,正整数,找出最短的连续子数组,求出长度最小的连续子序列.
         * 满足条件是  子序列的和 >=target
         * 求出长度最短的那个
         */
        if (nums.length == 0) {
            return 0;
        }
        int left = 0, right = 0;
        int minLength = Integer.MAX_VALUE;
        int sum = 0;
        while (right < nums.length) {
            sum += nums[right];
            //[left,right]
            while (sum >= target) {
                minLength = Math.min(minLength, (right - left + 1));
                sum -= nums[left];
                left++;
            }
            right++;
        }

        return minLength == Integer.MAX_VALUE ? 0 : minLength;

    }

}

如果使用 nlogn复杂度的算法.这个时候适用累加和,然后用二分查找累加和.

问题二 剑指 Offer II 009. 乘积小于 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.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {

        /**
         * 找出所有的连续子数组,其乘积 < target元素
         */
        int left = 0, right = 0;
        int result = 1;
        int count = 0;
        while (right < nums.length) {
            result *= nums[right];

            while (left <= right && result >= k) {
                result /= nums[left];
                left++;
            }
            //当前满足了 以nums[right]结尾的子序列的乘积 < k
            //这里 count += right - left +1 的思路是 以当前right结尾的所有的子序列.
            // 其中 左区间可以是 [right,left].所以是 额外添加的元素是 (right -left +1)
            count += right - left + 1;
            right++;
        }

        return count;
    }
}

问题三 剑指 Offer II 014. 字符串中的变位词

给定两个字符串s1和s2,写一个函数来判断 s2 是否包含 s1的某个变位词。

换句话说,第一个字符串的排列之一是第二个字符串的 子串 。

示例 1:

输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").
示例 2:

输入: s1= "ab" s2 = "eidboaoo"
输出: False

提示:
1 <= s1.length, s2.length <= 104
s1 和 s2 仅包含小写字母

class Solution {
   public boolean checkInclusion(String s1, String s2) {

        /**
         * s2是否包括s1的排列
         */

        int left = 0, right = 0;
        int[] chCount = new int[26];
        for (int i = 0; i < s1.length(); i++) {
            char ch = s1.charAt(i);
            chCount[ch - 'a']++;
        }

        while (right < s2.length()) {
            int rightRank = s2.charAt(right) - 'a';
            chCount[rightRank]--;
            while (chCount[rightRank] < 0) {
                int leftRank = s2.charAt(left) - 'a';
                chCount[leftRank]++;
                left++;
            }
            if (right - left + 1 == s1.length()) {
                return true;
            }
            right++;

        }
        return false;
    }
}

这个题首先不断的nums[right]添加窗口并统计计数.依次将nums[left]移出窗口直到窗口合规,然后校验最终结果是否符合条件.
这题是标准的模板实现.

问题四 剑指 Offer II 017. 含有所有字符的最短字符串

给定两个字符串 s 和t 。返回 s 中包含t的所有字符的最短子字符串。如果 s 中不存在符合条件的子字符串,则返回空字符串 "" 。
如果 s 中存在多个符合条件的子字符串,返回任意一个。
注意: 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC" 
解释:最短子字符串 "BANC" 包含了字符串 t 的所有字符 'A'、'B'、'C'
示例 2:

输入:s = "a", t = "a"
输出:"a"
示例 3:

输入:s = "a", t = "aa"
输出:""
解释:t 中两个字符 'a' 均应包含在 s 的子串中,因此没有符合条件的子字符串,返回空字符串。


提示:

1 <= s.length, t.length <= 105
s 和 t 由英文字母组成

进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
class Solution {
      public String minWindow(String s, String t) {

        /**
         * 这个题使用滑动窗口,首先适用右侧进行滑动,然后不断判断[left,right]是否满足了条件所有的字母个数都满足了条件.然后移动左侧.知道不满足为止.然后获取[left-1,right]为一个符合条件的字符串.
         * 然后右侧继续移动,知道满足条件为止.
         */

        Map<Character, Integer> targetChCountMap = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            char ch = t.charAt(i);
            targetChCountMap.put(t.charAt(i), targetChCountMap.getOrDefault(ch, 0) + 1);
        }
        Map<Character, Integer> chCountMap = new HashMap<>();

        int left = 0, right = 0;
        boolean isLegal = false;
        String minLenStr = null;
        while (right < s.length()) {
            if (!isLegal) {
                char rightCh = s.charAt(right);
                chCountMap.put(rightCh, chCountMap.getOrDefault(rightCh, 0) + 1);
                isLegal = isLegalStr(chCountMap, targetChCountMap);
                //right指向了下一个位置
                right++;
            }
            while (left <= right && isLegal) {
                char leftCh = s.charAt(left);
                chCountMap.put(leftCh, chCountMap.get(leftCh) - 1);
                isLegal = isLegalStr(chCountMap, targetChCountMap);
                if (!isLegal) {
                    String targetStr = s.substring(left, right);
                    if (minLenStr == null || minLenStr.length() > targetStr.length()) {
                        minLenStr = targetStr;
                    }
                }
                left++;
            }
        }
        return minLenStr == null ? "" : minLenStr;

    }

    private boolean isLegalStr(Map<Character, Integer> chCountMap, Map<Character, Integer> targetChCountMap) {

        for (Map.Entry<Character, Integer> entry : targetChCountMap.entrySet()) {
            Integer chCount = chCountMap.get(entry.getKey());
            if (chCount == null || chCount < entry.getValue()) {
                return false;
            }
        }
        return true;
    }

}

求cover了包含 t字符串的子串.这个题是这样做的:

1.将nums[right]不断添加到窗口中.直到满足条件
2.然后左移窗口,直到不满足条件来压缩窗口长度,找出最小长度的窗口
3.计算最终结果.
4.循环第一步

问题四 剑指 Offer II 057. 值和下标之差都在给定的范围内

给你一个整数数组 nums 和两个整数k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。

如果存在则返回 true,不存在返回 false。



示例1:

输入:nums = [1,2,3,1], k = 3, t = 0
输出:true
示例 2:

输入:nums = [1,0,1,1], k = 1, t = 2
输出:true
示例 3:

输入:nums = [1,5,9,1,5,9], k = 2, t = 3
输出:false


提示:

0 <= nums.length <= 2 * 104
-231 <= nums[i] <= 231 - 1
0 <= k <= 104
0 <= t <= 231 - 1

class Solution {
      public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {

        /**
         * 这个题是:给定一个长度为 k的滑动窗口. 找出 最后一个元素和之前的元素的差值在 [nums[i]-t,nums[i]+t].
         *
         */
        TreeSet<Long> window = new TreeSet<>();
        for (int i = 0; i < nums.length; i++) {
            //从window中找出一个 介于 [nums[i] - t , nums[i] +t]的元素.如果有就返回.
            Long ceiling = window.ceiling((long) nums[i] - (long) t); //找出 第一个 ceiling >= nums[i] -t的元素
            //判断这个元素是否 <= nums[i] + t
            if (ceiling != null && ceiling <= (long) nums[i] + (long)t) {
                //如果满足条件就返回false
                return true;
            }
            window.add((long) nums[i]);
            if (i >= k) {
                window.remove((long)nums[i - k]);
            }
        }
        return false;
    }
}

这里的区间有点不同,窗口不仅仅使用的是 nums[left,right],同时使用一个TreeSet将窗口数据添加进来,便于处理

标签:right,窗口,nums,int,++,算法,数组,滑动,left
来源: https://www.cnblogs.com/xxuuzz/p/16412296.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有