leetcode滑动窗口的相关题--(持续更新)

滑动窗口类型题

滑动窗口的最大值

题目:

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

实例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

思想:

这道题用暴力解决很简单,我限定一个窗口大小为K的窗口,窗口在数组上移动,每移动一次我就遍历一次窗口求最大值,显然这样的算法时间复杂度为O(n2)的,现在就需要有一种算法来将时间复杂度降为O(n)。滑动窗口算法就是对于这样解决的。

实现:

class Solution {
    
    
    public int[] maxSlidingWindow(int[] nums, int k) {
    
    
        if(nums == null || k<1 || nums.length < k ){
    
    
            return new int[0];
        }
        //声明一个双向队列,用于存放滑动窗口的值,但里面存的值是有要求的
        //首先这个双向队列存的值是数组中值对应的索引,而不是存值
        //其次这个双向队列一定要是从左到右的索引对应数组中的值一定要是从大到小递减的,相等都不行。
        LinkedList<Integer> qmax = new LinkedList<>();
        //下面这两个变量是用存储滑动窗口得最大值得
        int index = 0;
        int res[] = new int[nums.length-k+1];
        //遍历数组
        for(int i=0;i<nums.length;i++){
    
    
            //判断当前遍历数组的值是否可以达到入队列的要求,如果队列尾的元素比当前元素小或者相等的情况就必须让其出队列
            while(!qmax.isEmpty() && nums[qmax.peekLast()] <= nums[i]){
    
    
                qmax.pollLast();
            }
            //队列为空或者是队列尾的元素比当前值大,就可以入队列了
            qmax.addLast(i);
            //判断队列头部索引是否过期,如果过期,就将它从队列中删除
            if (qmax.peekFirst() == i-k){
    
    
                qmax.pollFirst();
            }
            //滑动窗口里面的值被填充满,开始计算滑动窗口中的最大值
            if (i >= k-1){
    
    
                res[index++] = nums[qmax.peekFirst()];
            }
        }
        return res;
    }
}

字符串排列

题目:

给定两个字符串 s1s2,写一个函数来判断 s2 是否包含 s1 的排列。

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

实例:

示例1:

输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).

示例2:

输入: s1= “ab” s2 = “eidboaoo”
输出: False

思想:

看到这个题,是不是就立马想到,求出S1的全排列结果然后再判断这些结果是否出现在S2中,哈哈,可以的,但是当你看到这个提示的时候,也许就不会这么想了。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2aKOmX9e-1596371504675)(…/image/1.png)]

咱们可以把它想成一个滑动窗口类型的题来做,怎么来呢?接下来整理一下思路:

  • 我们先定义两个大小为26的数组,它们用来存储两个字符串中出现的字符的数量
  • 再定义一个count用于判断两个数组的26个字母的数量是否一样,最后我们就是返回count==26,后面我们会解释为什么会这样返回
  • 然后我们先在S2中形成一个大小为S1长度的窗口,并且在遍历过程中将遍历到的元素个数分别统计到各自的数组中
  • 先遍历一次26个字母在两个数组中出现的情况,如果一个字母在两个数组中的出现次数一样,就count++;
  • 接着就开始在S2中进行窗口滑动了,首先会判断count==26吗,如果等于的话就直接返回true,接着我们让一个字母进来,就将该字母对应的S2中的数组数量+1,并且判断该字母对应S2数组出现的数量是否等于S1数组中的数量,如果相等就进行count++,如果S2数组中字母对应出现的数量等于S1数组中字母出现的数量+1,说明之前相等现在不相等了,就count–,最后我们会让窗口中左边的字母弹出,就将该字母对应的S2中的数组数量-1,并且判断该字母对应S2数组出现的数量是否等于S1数组中的数量,如果相等就进行count++,如果S2数组中字母对应出现的数量等于S1数组中字母出现的数量-1,说明之前相等现在不相等了,就count–,
  • 最后跳出循环,返回count==26,因为返回count最终是否等于26表示两个数组中的元素是一样的,所以就为true

代码:

class Solution {
    
    
    public boolean checkInclusion(String s1, String s2) {
    
    
        if(s1==null || s1.length()==0 || s2 == null || s2.length()==0 || s1.length() > s2.length()){
    
    
            return false;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        int []arr1 = new int[26];
        int []arr2 = new int[26];
        for (int i=0;i<str1.length;i++) {
    
    
            arr1[str1[i]-'a'] ++ ;
            arr2[str2[i]-'a'] ++ ;
        }
        int count = 0;
        for (int i=0;i<26;i++){
    
    
            if(arr1[i] == arr2[i]){
    
    
                count++;
            }
        }
        for (int i=str1.length;i<str2.length;i++){
    
    
            int r = str2[i]-'a',l=str2[i-str1.length]-'a';
            if (count == 26){
    
    
                return true;
            }
            arr2[r]++;
            if (arr1[r] == arr2[r]){
    
    
                count++;
            }else if(arr1[r]+1 == arr2[r]){
    
    
                count--;
            }
            arr2[l]--;
            if (arr1[l] == arr2[l]){
    
    
                count++;
            }else if(arr1[l]-1 == arr2[l]){
    
    
                count--;
            }
        }
        return count==26;
    }
}

最大连续1的个数

题目:

给定一个由若干 01 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。

返回仅包含 1 的最长(连续)子数组的长度。

实例:

示例 1:

输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:

输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:
[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。

提示:

1 <= A.length <= 20000
0 <= K <= A.length
A[i] 为 0 或 1

思想:

这个题也是可以用滑动窗口来做的。

我们可以用以historySize来记录窗口中1出现次数的历史最大值,刚开始先形成一个historySize+k的窗口大小,然后当窗口大小超过该值的时候,我们需要将左边界向右移动一个位置,不管窗口大小是否超过该值,我们每次遍历都将窗口向右扩一个,接下来的细节操作,我门来看下代码

代码:

class Solution {
    
    
    public int longestOnes(int[] A, int K) {
    
    
        if (A == null || A.length == 0 || K < 0){
    
    
            return 0;
        }
        int max = 0;//历史1的最大值
        int left = 0;
        int right = 0;
        int []arr = new int[2];
        for(right=0;right<A.length;right++){
    
    
            arr[A[right]]++;
            max = Math.max(max,arr[1]);
            if (right-left+1 > max+K){
    
    
                arr[A[left]]--;
                left ++;
            }
        }
        return right-left;
    }
}

可获得的最大点数

题目:

几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。

每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。

你的点数就是你拿到手中的所有卡牌的点数之和。

给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。

实例:

示例 1:

输入:cardPoints = [1,2,3,4,5,6,1], k = 3
输出:12
解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。

示例 2:

输入:cardPoints = [2,2,2], k = 2
输出:4
解释:无论你拿起哪两张卡牌,可获得的点数总是 4 。
示例 3:

输入:cardPoints = [9,7,7,9,7,7,9], k = 7
输出:55
解释:你必须拿起所有卡牌,可以获得的点数为所有卡牌的点数之和。
示例 4:

输入:cardPoints = [1,1000,1], k = 1
输出:1
解释:你无法拿到中间那张卡牌,所以可以获得的最大点数为 1 。
示例 5:

输入:cardPoints = [1,79,80,1,1,1,200,1], k = 3
输出:202

提示:

1 <= cardPoints.length <= 10^5
1 <= cardPoints[i] <= 10^4
1 <= k <= cardPoints.length

思想:

这个题是如何转化成一个滑动窗口的思想的,我们来说一下,题目中说到,我们每次选择都是选当前数组左右两个数字的其中一个来求最大值,那么我们没有选择的数字在这个数组中一定会是连续的,所以我们的滑动窗口来定义没有被选择的数字,我们使窗口滑动来更新滑动窗口的最小值,最后返回整个数组的和-滑动窗口中元素的和的最小值

代码:

class Solution {
    
    
    public int maxScore(int[] cardPoints, int k) {
    
    
        if(cardPoints == null || k < 0){
    
    
            return 0;
        }
        int sum = 0;
        int min = 0;
        int maxSum = 0;
        int r = -1;
        for (int i=0;i<cardPoints.length-k;i++){
    
    
            sum += cardPoints[i];
        }
        min = sum;
        maxSum = sum;
        for(int i=cardPoints.length-k;i<cardPoints.length;i++){
    
    
            sum = sum + cardPoints[i]-cardPoints[i-cardPoints.length+k];
            maxSum += cardPoints[i];
            min = min < sum ? min : sum;
        }
        return maxSum-min;
    }
}

最长不含重复字符的子字符串

题目:

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

实例:

示例 1:

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

提示:

s.length <= 40000

思想:

我们需要借助一个哈希表来存储不重复字符的子字符串,接下来分析下思想

  • 我们遍历数组,每次都以遍历的字符为滑动窗口的起始值,来将窗口向右扩,向右扩的条件是哈希表中没有出现过这个字符
  • 维护一个最大值,记录滑动窗口的最大值
  • 最终返回最大值

代码:

class Solution {
    
    
    public int lengthOfLongestSubstring(String s) {
    
    
        if (s == null || s.length() == 0) {
    
    
            return 0;
        }
        char[] str = s.toCharArray();
        Set<Character> set = new HashSet<>();
        int r = -1;
        int max = 0;
        for(int i=0;i<str.length;i++){
    
    
            if (i!=0){
    
    
                set.remove(str[i-1]);
            }
            while(r+1 < str.length && !set.contains(str[r+1])){
    
    
                set.add(str[r+1]);
                r++;
            }
            max = max>r-i+1?max:r-i+1;
        }
        return max;
    }
}

替换后的最长重复字符

题目:

给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。

注意:
字符串长度 和 k 不会超过 104。

实例:

示例 1:

输入:
s = “ABAB”, k = 2

输出:
4

解释:
用两个’A’替换为两个’B’,反之亦然。

示例 2:

输入:
s = “AABABBA”, k = 1

输出:
4

解释:
将中间的一个’A’替换为’B’,字符串变为 “AABBBBA”。
子串 “BBBB” 有最长重复字母, 答案为 4。

思想:

这个题就是上面那个最大连续1的个数题的进阶版本,我们维护一个长度为26大小的数组,用于统计每个字母出现的数量,也维护一个historySize,这个变量就不只是记录一个字符在窗口中出现的历史最大值了,它是窗口中出现过的字符的历史最大值,一旦数组中另外一个字母的数量超过了这个,就需要更新这个历史最大值了。跟那个题一样,我们在向右遍历的过程中需要形成一个historySize+k大小的窗口,但是一旦超过了这个大小,就需要将窗口的左边界向右移动一个单位,最终数组遍历完毕,返回滑动窗口的size

代码:

class Solution {
    
    
    public int characterReplacement(String s,int k){
    
    
        if (s == null){
    
    
            return 0;
        }
        char []str = s.toCharArray();
        int []arr = new int[str.length];
        int left = 0;
        int right = 0;
        int max = 0;
        for(right=0 ; right < str.length ; right ++){
    
    
            int index = str[right] - 'A';
            arr[index] ++ ;
            max = Math.max(max,arr[index]);
            if (right - left + 1 > max + k){
    
    
                arr[str[left]-'A']--;
                left ++ ;
            }
        }
        return right-left;
    }
}

猜你喜欢

转载自blog.csdn.net/MarkusZhang/article/details/107750553