滑动窗口

滑动窗口模板

....
int left = 0, right = 0;
//len为需要滑动的窗口的长度
while(right < len) {
  ...
  right++;
  while或者if() {
    left++;  
  }
}

题型一: 求一个字符串的字串,子串中至多有2,n个不重复的字符,或者字符串中无重复字符

题型二: 替换k个字符后最长的连续子串

题型三: 有2个字符串s和t,求s中能包含t中所有的字母
题型四: 求连续子数组的最大或者最小值

3.无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

方法一:

 public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() == 0) return 0;

        int len = s.length();
        int res = 1;
        char[] chs = s.toCharArray();
        HashMap<Character,Integer> hash = new HashMap<>();

        int i = 0, j = 0;
        while(j < len) {
            char ch = chs[j];
            if(hash.containsKey(ch)) {
                i = Math.max(i,hash.get(ch) + 1);
            }
            hash.put(ch,j);
            j++;
            res = Math.max(res,j - i);
        }
        return res;

    } 

 方法二:


  public int lengthOfLongestSubstring(String s) {
        if(s == null || s.length() == 0return 0;
        int len = s.length();
        int res = 1;
        char[] chs = s.toCharArray();
        int[] hash = new int[128];

        int left = 0, right = 0;
        while(right < len) {
            int index = chs[right];
            hash[index] += 1;
            right++;        
            //判断right元素放进去以后是否满足条件
            while(hash[index] != 1) {
                hash[chs[left++]]--;
            }
            res = Math.max(res,right - left);
        }
        return res;
    }

159.至多包含两个不同字符的最长子串

题目描述:给定一个字符串 s ,找出 至多 包含两个不同字符的最长子串 t 。
示例 1:
输入: “eceba”
输出: 3
解释: t 是 “ece”,长度为3。

public int lengthOfLongestSubstringTwoDistinct(String s) {
        //代表当前出现的字符数量
        int cnt = 0;
        int ans = 0;
        int len = s.length();

        char[] chs = s.toCharArray();
        int hash[] = new int[128];
        int left = 0, right = 0;

        while (right < len) {
       //说明right的元素是第一次出现
if(hash[chs[right]] == 0) { cnt++; } hash[chs[right++]]++;
       //cnt代表了现在一共有的字符数
while (left < len && cnt > 2) { hash[chs[left]]--; if(hash[chs[left++]] == 0) { cnt--; } } if(len == left) break; ans = Math.max(ans,right - left); } return ans; }

424.替换后的最长重复字串  

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

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

public int characterReplacement(String s, int k) {
        int sLen = s.length();
        //代表了目前滑动窗口的最大值
        int curMax = 0;
        int ans = 0;
        //表示当前字符串中各个字符出现的次数
        int[] hash = new int[26];
        char[] chs = s.toCharArray();
        int left = 0, right = 0;

        while (right < sLen) {
            int index = chs[right] - 'A';
            hash[index]++;
        //curMax表示重复的最多个数 curMax
= Math.max(curMax,hash[index]); right++; //窗口左移 while(right - left - curMax > k) { hash[chs[left++] - 'A']--; } ans = Math.max(ans,right - left); } return ans; }

567.字符串的排列

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

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

示例1:

输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").
public boolean checkInclusion(String s1, String s2) {
        int s1Len = s1.length();
        int s2Len = s2.length();
        char[] charS1 = s1.toCharArray();
        char[] charS2 = s2.toCharArray();
        int need[] = new int[26];
        int cur[] = new int[26];
        int distance = 0;   
        int left = 0, right = 0;

        for(int i = 0; i < s1Len; i++) {
            need[charS1[i] - 'a']++;
        }
        while(right < s2Len) {
            int index = charS2[right] - 'a';
            //说明还是我们需要的
            if(cur[index] < need[index]) {
                distance++;
            }
            cur[index]++;
            right++;
            //说明满足了
            while(distance == s1Len) {
                //因为这边要是连续的
                if(right - left == s1Len) return true;
                //代表左边界的下界
                int index1 = charS2[left] - 'a';
                if(cur[index1] <= need[index1]) {
                    distance--;
                }
                cur[index1]--;
                left++;
            }
        }
        return false;
    }

76. 最小覆盖子串

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

解法一:

public String minWindow(String s, String t) {
        if(t == null || t.length() == 0) return "";
        int sLen = s.length();
        int tLen = t.length();
        char[] chs = s.toCharArray();
        char[] cht = t.toCharArray();
        //滑动窗口中出现元素的频率
        int[] winFreq = new int[128];
        //t字符串中出现元素的频率
        int[] tFreq = new int[128];
        int left = 0, right = 0;
        int distance = 0;
        int minLen = sLen + 1;
        int minLeft = -1;

        //更新tFreq
        for(int i = 0; i < tLen; i++) {
            tFreq[cht[i]]++;
        }

        while(right < sLen) {
            //如果当前右边界元素没有出现过
            if(tFreq[chs[right]] == 0) {
                right++;
                continue;
            }
            if(winFreq[chs[right]] < tFreq[chs[right]]) {
                distance++;
            }
            winFreq[chs[right++]]++;

            ///说明覆盖成功
            while(distance == tLen) {
                if((right - left) < minLen) {
                    minLen = right - left;
                    minLeft = left;
                }
                if(tFreq[chs[left]] == 0) {
                    left++;
                    continue;
                }
                if(winFreq[chs[left]] == tFreq[chs[left]]) {
                    distance--;
                }
                winFreq[chs[left]]--;
                left++;
            }
        }
        if(minLen == sLen + 1) {
            return "";
        }
        return s.substring(minLeft, minLen + minLeft);
    }

解法二: 对解法一做了精简,只贴出不一样的地方

 while(right < sLen) {
            if(winFreq[chs[right]] < tFreq[chs[right]]) {
                distance++;
            }
            winFreq[chs[right++]]++;

            ///说明覆盖成功
            while(left < sLen && distance == tLen) {
                if((right - left) < minLen) {
                    minLen = right - left;
                    minLeft = left;
                }
                if(winFreq[chs[left]] == tFreq[chs[left]]) {
                    distance--;
                }
                winFreq[chs[left++]]--;
            }
        }

1234. 替换字串得到平衡字符串

有一个只含有 'Q', 'W', 'E', 'R' 四种字符,且长度为 n 的字符串。假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。

给你一个这样的字符串 s,请通过「替换子串」的方式,使原字符串 s 变成一个「平衡字符串」。你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。

请返回待替换子串的最小可能长度。如果原字符串自身就是一个平衡字符串,则返回 0

分析:一眼看过去和滑动窗口没有关系,但是可以使其转为找到一个窗口,窗口中需要包含某些字符,从而转为最小覆盖字串的问题

public int balancedString(String s) {
        int sLen = s.length();
        int res = sLen - 1;
        int left = 0, right = 0;
        int[] need = new int[26];
        int[] cur = new int[26];
        char[] chs = s.toCharArray();
        int distance = 0;

        Arrays.fill(need, -sLen/4);
        for(int i = 0; i < sLen; i++) {
            need[chs[i] - 'A']++;
        }
     //得到需要转换的字符串各个字符的数量
for(int i = 0; i < 26; i++) { if(need[i] > 0) distance += need[i]; } if(distance == 0) return 0; while(right < sLen) { int index = chs[right] - 'A'; if(need[index] > 0) { if(cur[index] < need[index]) { distance--; } cur[index]++; } right++; while(left < sLen && distance == 0) { res = Math.min(res,right - left); int index1 = chs[left] - 'A'; if(need[index1] > 0) { if(cur[index1] == need[index1]) { distance++; } cur[index1]--; } left++; } } return res; }

438. 找到字符串中所有字母异位词

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 sp 的长度都不超过 20100。

分析:和567题一样

public List<Integer> findAnagrams(String s, String p) {
        List<Integer> res = new ArrayList();
        int sLen = s.length();
        int pLen = p.length();
        char[] charsS = s.toCharArray();
        char[] charsP = p.toCharArray();
        int[] cur = new int[26];
        int[] need = new int[26];
        int left = 0, right = 0;
        int distance = 0;

        for(int i = 0; i < pLen; i++) {
            need[charsP[i] - 'a']++;
        }
 
        while(right < sLen) {
            int indexRight = charsS[right] - 'a';
            if(cur[indexRight] < need[indexRight]) {
                distance++;
            }
            cur[indexRight]++;
            right++;
            //如果满足条件,尝试将窗口左移
            while(distance == pLen) {
                if(right - left == pLen) {
                    res.add(left);
                }
                int indexLeft = charsS[left] - 'a';
                if(cur[indexLeft] <= need[indexLeft]) {
                    distance--;
                }
                cur[indexLeft]--;
                left++;
            }
        }
        return res;
    }

30.串联所有单词的字串

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

解法一:暴力解法

public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        if (s == null || s.length() == 0 || words == null || words.length == 0) return res;

        HashMap<String, Integer> map = new HashMap<>();
        int one_word = words[0].length();
        int word_num = words.length;
        int all_len = one_word * word_num;
        for (String word : words) {
            map.put(word,map.getOrDefault(word,0) + 1);
        }
        for (int i = 0; i < s.length() - all_len + 1; i++) {
            String tmp = s.substring(i, i + all_len);
            HashMap<String,Integer> tmp_map = new HashMap<>();
            for (int j = 0; j < all_len; j+=one_word) {
                String w = tmp.substring(j,j + one_word);
                if(!map.containsKey(w)) break;
                tmp_map.put(w,tmp_map.getOrDefault(w,0) + 1);
            }
            if((map.size() == tmp_map.size()) && map.equals(tmp_map)) res.add(i);
        }
        return res;
    }

解法二:滑动窗口

 public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        if (s == null || s.length() == 0 || words == null || words.length == 0return res;
        int sLen = s.length();
        //代表了一个单词的长度
        int word_len = words[0].length();
        //代表了单词的数量
        int word_nums = words.length;
        int total = word_len * word_nums;
 
        //代表我们要查找的hash表
        HashMap<String, Integer> map = new HashMap<>();
        for (String word : words) {
            map.put(word, map.getOrDefault(word, 0) + 1);
        }

        //滑动窗口的起点有可能是第一个单词的任意位置
        for(int i = 0; i < word_len; i++) {
            int left = i, right = i, distance = 0;
            HashMap<String, Integer> tmp_map = new HashMap<>();
            //right为最后一个单词的起始位置,并不是倒数第二个单词的结束位置
            while(right + word_len <= sLen) {
                String cur = s.substring(right, right + word_len);
                if(!map.containsKey(cur)) {
                    distance = 0;
                    tmp_map.clear();
                    right += word_len;
                    left = right;
                }else {
                    if(tmp_map.getOrDefault(cur, 0) < map.get(cur)) {
                        distance++;
                    }
                    tmp_map.put(cur, tmp_map.getOrDefault(cur, 0) + 1);
                    right += word_len;
                }
                while(distance == word_nums) {
                    String curLeft = s.substring(left, left + word_len);
                    if(right - left == total) {
                        res.add(left);
                    }
                    if(tmp_map.get(curLeft) == map.get(curLeft)) {
                        distance--;
                    }
                    tmp_map.put(curLeft, tmp_map.get(curLeft) - 1);
                    left += word_len;
                }
            
            }
        }
        return res;
    }

209.长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组如果不存在符合条件的连续子数组,返回 0。

示例:

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
public int minSubArrayLen(int s, int[] nums) {
        if(nums == null || nums.length == 0) return 0;
        int left = 0, right = 0;
        int len = nums.length;
        int minLen = len + 1;
        int sum = 0;

        while(right < len) {
            sum += nums[right];
            right++;
            while(sum >= s) {
                minLen = Math.min(minLen,right - left);
                sum -= nums[left];
                left++;
            }
        }
        if(minLen == len + 1) {
            return 0;
        }
        return minLen;
    }

1423.可获得的最大点数

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

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

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

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

分析:这题滑动窗口用的比较巧妙,要求的是点数的最大值,我们只需要求对应连续固定滑动窗口的最小值(和209题相似),然后相减就可以了

public int maxScore(int[] cardPoints, int k) {
        int len = cardPoints.length;
        int sum = 0;
        int left = 0, right = 0;
        int curSum = 0;
        //代表了要维护的滑动窗口的大小
        int slipWindowLen = len - k;
        int minSum = Integer.MAX_VALUE;

        for(int i = 0; i < len; i++) {
            sum += cardPoints[i];
        }

        if(k == len) {
            return sum;
        }

        while(right < len) {
            curSum += cardPoints[right];
            right++;
            //说明了滑动窗口中的元素已经达到了我们需要的值,需要移动来维护滑动窗口
            if(right > slipWindowLen) {
                curSum -= cardPoints[left++];
            }
            if(right >= slipWindowLen) {
                minSum = Math.min(minSum,curSum);
            }
        }
        return sum - minSum;
    }

1052.爱生气的老板

今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。

在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。

书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。

请你返回这一天营业下来,最多有多少客户能够感到满意的数量。

示例:

输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3
输出:16
解释:
书店老板在最后 3 分钟保持冷静。
感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.

分析:要求的是X分钟不生气的最大值,可以转换为在滑动窗口里面由于不生气转换来的最大值,和1423题一样


 public int maxSatisfied(int[] customers, int[] grumpy, int X) {
        int customersLen = customers.length;
        int grumpyLen = grumpy.length;
        int right = 0, left = 0;
        //代表老板不生气时候的总人数
        int sum = 0;
        //老板在生气时挽救回来的人数
        int sumGrumpy = 0;
        //老板在生气时挽救回来的最大值
        int w = 0;
        for(int i = 0; i < customersLen; i++) {
            if(grumpy[i] == 0) {
                sum += customers[i];
            } 
        }

        while(right < customersLen) {
            if(grumpy[right] == 1) {
                sumGrumpy += customers[right];
            }  
            right++;
            if(right > X) {
                if(grumpy[left] == 1) {
                    sumGrumpy -= customers[left];
                }
                left++;
            }
            if(right >= X) {
                w = Math.max(w,sumGrumpy);
            }          
        }
        return sum + w;
    }
posted @ 2020-06-28 10:22  fight12346789  阅读(222)  评论(0编辑  收藏  举报