算法通关村第十六关——滑动窗口白银挑战笔记

在处理滑动窗口问题中,窗口边界的移动逻辑是关键!该部分我们针对滑动窗口中高频考点,继续梳理窗口边界的移动逻辑,从而游刃有余的面对接下来的挑战!此外,该部分还针对最长子串问题异位词问题最小子数组问题分别总结了模板以加深理解!

1.最长子串专题

最长字串问题特别喜欢考察滑动窗口思想,如:在给定的字符串S中,找到不含重复字符的最长字串长度。针对该问题,我们还可以继续扩展考察:至多包含2个不同字符的最长字串长度至多包含K个不同字符的最长字串长度!接下来,针对最长字串这一专题,我们详细分析滑动窗口的边界问题,总结滑动窗口解题模板

1.1无重复字符的最长字串

题目见LeetCode3,描述为:给定字符串s,找到不含重复字符的最长字串长度。

最长子串的模板:我们使用map来记录当前已经访问的字符串和它们的下标,即{key=字符,value=下标}。

题目分析:首先,创建窗口边界left、rightright不断遍历新元素,left不断调整窗口位置。当right遍历的元素存在于map中,说明下一时刻出现了重复字符,我们需要加入当前遍历的元素right,同时调整窗口边界left的值。

关键在于窗口边界left如何调整?答:取发生重复元素的下一个位置,比如abcefcc当right再次遍历到c时,子串出现重复字符,原来的子串abcef长度已经记录,因此需要更换成新的子(efc)继续向后寻找无重复字符的最长字串,其逻辑就是left = map.get(s.charAt(right))+1。

特例情况:当abba,第二次访问b时,窗口边界left应该变成2,第二次访问a时,窗口边界left会发生“回退”变成1,因此left = Math.max(map.get(s.charAt(right))+1, left)。

结合代码,继续理解上述分析!

    public static int myLengthOfLongestSubstring(String s) {
        if(s.length() == 0 || s == null) return 0;
        int left = 0;
        int max = 0;
        HashMap<Character, Integer> map = new HashMap<>();
        for (int right = 0; right < s.length(); right++) {
            //发生重复
            if(map.containsKey(s.charAt(right))){
                left = Math.max(map.get(s.charAt(right))+1, left);
            }
            //增加未出现字符或修改已出现字符
            map.put(s.charAt(right), right);
            max = Math.max(right - left + 1, max);
        }
        return max;
    }

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

题目见LeetCode159,描述为:找出至多包含两个不同字符的最长子串t,并返回长度。

题目分析:应用最长子串的模板,直接读取前两个字符,然后使用right继续遍历剩余的字符,当map的容量小于3时,可以将当前遍历字符加入map;当map的容量等于3时,map移除之前的一个元素且窗口边界left发生移动!

关键在于,移除哪一个元素?窗口边界如何移动?答:移除窗口边界最左侧的元素,具体来说就是获取窗口最左侧元素的索引,从map中删除最左侧元素,移动窗口边界left=index+1。

结合代码,继续理解上述分析!

    public static int myLengthOfLongestSubstringTwoDistinct(String s) {
        int left = 0, right = 0;
        int maxLen = 2;
        HashMap<Character, Integer> map = new HashMap<>();
        if(s.length() < 3) return s.length();
        while(right < s.length()){
            if(map.size() < 3){
                map.put(s.charAt(right), right++);
            }
            if(map.size() == 3){
                //找到滑动窗口最左侧元素下标
                int index = Collections.min(map.values());
                map.remove(s.charAt(index));
                left = Math.max(left, index + 1);
            }
            maxLen = Math.max(maxLen, right - left);
        }
        return maxLen;
    }

1.3至多包含K个不同字符的最长子串

题目见LeetCode340,描述为:找出至多包含K个不同字符的最长子串t,并返回长度。

题目分析:可以说1.3就是1.2“照猫画虎”衍生出来的,那么我们的解决办法也是“依葫芦画瓢”,直接只需要将原来map大小判断由3改成K+1即可!直接上代码!

    public static int myLengthOfLongestSubstringKDistinct(String s, int k) {
        if(s.length() < k + 1) return s.length();
        int left = 0, right = 0;
        int maxLen = k;
        HashMap<Character, Integer> map = new HashMap<>();
        while(right < s.length()){
            if(map.size() < k + 1){
                map.put(s.charAt(right), right++);
            }
            if(map.size() == k+1){
                int index = Collections.min(map.values());
                map.remove(s.charAt(index));
                left = index + 1;
            }
            maxLen = Math.max(right - left, maxLen);
        }
        return maxLen;
    }

2.长度最小的子数组

题目见LeetCode209,描述为:给定一个含有n个正整数的数组和一个正整数target,找到该数组中满足和>=target的长度最小的连续子数组,并返回其长度。

题目分析:使用right遍历数组,left调整窗口边界。“队列法”模板:让元素不断入队,当入队元素和>=target时,记录队列容量,然后调整窗口边界left使窗口不断缩小,直到窗口内元素和小于target,再让新元素入队!厘清思路,直接上代码!

    public static int myMinSubArrayLen(int target, int[] nums) {
        int left = 0, right = 0;
        int minLen = Integer.MAX_VALUE, sum = 0;
        while(right < nums.length){
            sum += nums[right++];
            while(sum >= target){
                minLen = Math.min(minLen, right - left);
                sum -= nums[left++];
            }
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }

3.盛水最多的容器

题目见LeetCode209。

题目分析:从最大窗口开始,不断缩小窗口,计算何时容器盛水最多!

关键在于,如何缩小窗口?答:选取窗口边界较小的短板,窗口在较小短板处向内收缩!

原理:若窗口在较大短板处向内收缩,会使得新窗口的底边首先变小,然后新窗口的较小短板不变或变小,水槽的面积一定变小!若窗口在较小短板处向内收缩,虽然使得新窗口的底边首先变小,但新窗口的较小短板可能变大,水槽的面积可能变大!因此,窗口在较小短板处向内收缩!

厘清思路,直接上代码!

    public static int maxArea(int[] height) {
        int i = 0, j = height.length - 1, res = 0;
        while (i < j) {
            res = height[i] < height[j] ?
                    Math.max(res, (j - i) * height[i++]) :
                    Math.max(res, (j - i) * height[j--]);
        }
        return res;
    }

4.寻找子串异位词

什么是异位词?如果两个字符串仅仅字母出现顺序不同,则这两个字符串互称“异位词”(考场不知道的话,心态首先输了一半!)

那么,如何判断两个词是否是异位词呢?答:排序一下就可以!(方法是可行的,但是排序的时间消耗难以保证)别灰心!异位词的判断模板不就来了嘛!

异位词的判断模板:创建两个长度为26的int数组,charArray[i]表示字符('a'+i)出现的次数,当两个int数组相等,则两个字符串就是异位词,判断数组相等可以使用Arrays.equals(nums1,nums2)方法!

4.1字符串的排列

题目见LeetCode567,描述为:给定两个字符串s1和s2,写一个函数判断s2是否包含s1的排列。

题目分析:就是判断s1是否是s2子串的异位词!应用模板,直接上代码!

    public static boolean myCheckInclusion(String s1, String s2) {
        int len1 = s1.length(), len2 = s2.length();
        if(len1 > len2) return false;
        int[] charArray1 = new int[26];
        int[] charArray2 = new int[26];
        for (int i = 0; i < len1; i++) {
            charArray1[s1.charAt(i)-'a']++;
            charArray2[s2.charAt(i)-'a']++;
        }
        if(Arrays.equals(charArray1, charArray2)) return true;
        for (int right = len1; right < len2; right++) {
            charArray2[s2.charAt(right)-'a']++;
            int left = right - len1;
            charArray2[s2.charAt(left)-'a']--;
            if(Arrays.equals(charArray1,charArray2)) return true;
        }
        return false;
    }

4.2找到字符串中所有的异位词

题目见LeetCode438,描述为:给定两个字符串s和p,找到s中所有p的异位词的子串,并返回这些子串的起始索引。

题目分析:4.2和4.1同根同源,只不过多了一步记录并返回子串起始索引的任务!那么,在判断出是异位词的时候,保存子串的其实索引即可!厘清思路,直接上代码!

    public static List<Integer> myFindAnagrams(String s, String p) {
        int sLen = s.length(), pLen = p.length();
        ArrayList<Integer> res = new ArrayList<>();
        if(s.length()<p.length()) return res;
        int[] sArray = new int[26];
        int[] pArray = new int[26];
        for (int i = 0; i < pLen; i++) {
            sArray[s.charAt(i)-'a']++;
            pArray[p.charAt(i)-'a']++;
        }
        if(Arrays.equals(sArray,pArray)) res.add(0);
        for (int right = pLen; right < sLen; right++) {
            sArray[s.charAt(right)-'a']++;
            int left = right - pLen;
            sArray[s.charAt(left)-'a']--;
            if(Arrays.equals(sArray, pArray)) res.add(left + 1);
        }
        return res;
    }

OK,《算法通关村第十六关——滑动窗口白银挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)

再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴!ok,拜拜,第十六关第三幕见!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值