Skip to content

Latest commit

 

History

History
303 lines (228 loc) · 11.4 KB

File metadata and controls

303 lines (228 loc) · 11.4 KB

滑动窗口算法

遇到子串问题,首先想到的就是滑动窗口技巧

总结出滑动窗口算法的抽象思想:

int left = 0, right = 0;

while (right < s.size()) {
    window.add(s[right]);
    right++;
    
    while (valid) {
        window.remove(s[left]);
        left++;
    }
}

滑动窗口算法的思路是这样:

1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。

2、我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。

4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

题目列表

3 无重复字符的最长子串

两种思路,一种用 set(前两块代码) ,一种用map(最后一块)

public int lengthOfLongestSubstring(String s){
    //        定义返回的最大长度,左右指针
            int Max=0,left=0,right=0;
            char[] charArray=s.toCharArray();
    //        Set 去重
            Set<Character> set=new HashSet<>();
    
            for (int i = 0; i < s.length(); i++) {
    //            如果当前字母加入窗口中会造成重复,则不断移动左指针直到没有重复字母为止
                while (set.contains(charArray[i])) set.remove(charArray[left++]);
    //            当不会造成重复时加入
                set.add(charArray[i]);
                int temp=right-left+1;
                right++;//right 指针右移
    //            尝试更新最大值
                Max=temp>Max?temp:Max;
            }
            return Max;
        }
//使用 window 作为计数器记录窗口中的字符出现次数,然后先向右移动 right,当 window 中出现重复字符时,开始移动 left 缩小窗口,如此往复
class Solution {
    public int lengthOfLongestSubstring(String s) {

        int max = 0,left = 0,right = 0;
        
        Set set = new HashSet<>();
        
        for (int i = 0; i < s.length(); i++) {
            while (set.contains(s.charAt(i))) {
                //缩小窗口,调整左指针
                //如果当前字母加入窗口中会造成重复,则不断移动左指针直到没有重复字母为止
                set.remove(s.charAt(left));
                left++;
            }
            //当不会造成重复时加入
            set.add(s.charAt(i));
            max = Math.max(max, i - left + 1);

        }    
        return max ;
    }
    public int lengthOfLongestSubstring(String s) {
        HashMap<Character, Integer> map = new HashMap<>();
        int maxLen = 0;//用于记录最大不重复子串的长度
        int left = 0;//滑动窗口左指针
        for (int i = 0; i < s.length() ; i++)
        {
            
            1首先判断当前字符是否包含在map中如果不包含将该字符添加到map字符字符在数组下标),
             此时没有出现重复的字符左指针不需要变化此时不重复子串的长度为i-left+1与原来的maxLen比较取最大值2如果当前字符 ch 包含在 map中此时有2类情况1当前字符包含在当前有效的子段中abca当我们遍历到第二个a当前有效最长子段是 abc我们又遍历到a那么此时更新 left  map.get(a)+1=1当前有效子段更新为 bca2当前字符不包含在当前最长有效子段中abba我们先添加a,b进map此时left=0我们再添加b发现map中包含b而且b包含在最长有效子段中就是1的情况我们更新 left=map.get(b)+1=2此时子段更新为 b而且map中仍然包含amap.get(a)=0随后我们遍历到a发现a包含在map中且map.get(a)=0如果我们像1一样处理就会发现 left=map.get(a)+1=1实际上left此时
             应该不变left始终为2子段变成 ba才对为了处理以上2类情况我们每次更新leftleft=Math.max(left , map.get(ch)+1).
             另外更新left后不管原来的 s.charAt(i) 是否在最长子段中我们都要将 s.charAt(i) 的位置更新为当前的i因此此时新的 s.charAt(i) 已经进入到 当前最长的子段中if(map.containsKey(s.charAt(i)))
            {
                left = Math.max(left , map.get(s.charAt(i))+1);
            }
            //不管是否更新left,都要更新 s.charAt(i) 的位置!
            map.put(s.charAt(i) , i);
            maxLen = Math.max(maxLen , i-left+1);
        }
        
        return maxLen;
    }

76 最小覆盖子串

class Solution {

  // 滑动窗口
    public String minWindow(String s, String t) {

        Map<Character,Integer> window = new HashMap(); // 存储当前窗口中字符及其个数
        Map<Character,Integer> need = new HashMap(); // 存储字符串t中字符及其个数

        for(char c : t.toCharArray()) need.put(c,need.getOrDefault(c,0)+1); // 存放字符串t
        
        int left = 0, right = 0; // 窗口左右指针(左闭右开)
        int start = 0, len = Integer.MAX_VALUE; // 存储最终结果的起始下标与长度.
        int valid = 0; // 表示window中包含need中字符的个数
        while(right < s.length()){
            char ch = s.charAt(right); // 当前位置字符
            right++; // 由于是左闭右开[0,0),最开始时是没有元素的,需要更新窗口

            if(need.containsKey(ch)){ // 扩大窗口
                window.put(ch,window.getOrDefault(ch,0)+1); // 如果字符串t包含ch,则更新窗口中的字符
                // 只有window中与need同时包含该字符,则个数也相同时,才更新valid
                // 使用equals判断,因为Integer是对象, == 判断的是内存地址,equals重写后判断的是内容
                if(window.get(ch).equals(need.get(ch))) valid++;
            }
            // 判断是否收缩左窗口
            while(valid == need.size()){ // 说明当前已满足s包含t,但不一定是最小的窗口

                // 更新最小覆盖子串
                if(right - left < len){ // 如果当前窗口的长度小于前一个窗口,则收缩窗口
                    start = left;
                    len = right - left;
                }
                // 窗口最左端的字符
                char remove = s.charAt(left);
                left++; // 开始收缩
                if(need.containsKey(remove)){
                    // 使用equals判断,因为Integer是对象, == 判断的是内存地址,equals重写后判断的是内容
                    if(window.get(remove).equals(need.get(remove))){ 
                        valid--;
                    }
                    window.put(remove,window.get(remove)-1); // 此时window一定包含remove
                }
            }
            
        }
        return len == Integer.MAX_VALUE ? "" : s.substring(start,start+len);
    }
}

567.字符串的排列

 public static boolean checkInclusion(String s1, String s2) {

        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();

        for (Character c : s1.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        int left = 0, right = 0;
        int vaild = 0;

        while (right < s2.length()) {

            Character c = s2.charAt(right);

            if (need.containsKey(c)) {
                window.put(c, window.getOrDefault(c, 0) + 1);
                if (window.get(c).equals(need.get(c))) {
                    vaild++;
                }
            }

            while (need.size() == vaild) {

                //长度一致可以返回
                if (right - left + 1 == s1.length()) {
                    return true;
                }

                Character remove = s2.charAt(left);
                if (need.containsKey(remove)) {
                    if (window.get(remove).equals(need.get(remove))) {
                        vaild--;
                    }
                    window.put(remove, window.getOrDefault(remove, 0) - 1);
                }
                //移动左指针
                left++;

            }
            //移动右指针
            right++;
        }

        return false;

    }

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

/**
 * 因为这道题和[76.最小覆盖子串]的场景类似,也需要 window 中包含串 t 的所有字符,
  但上一道题要找长度最短的子串,这道题要找长度相同的子串,也就是「字母异位词」嘛。

 */
class Solution {
    public List<Integer> findAnagrams(String s, String p) {

        HashMap<Character, Integer> window = new HashMap<>();
        HashMap<Character, Integer> need = new HashMap<>();

        int left = 0, right = 0;

        List res = new ArrayList<>();

        int vaild = 0;

        //初始化 need
        for (Character c : p.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        while (right < s.length()) {

            Character c = s.charAt(right);
            right++;
            if (need.containsKey(c)) {
                //扩大窗口
                window.put(c, window.getOrDefault(c, 0) + 1);

                if (window.get(c).equals(need.get(c))) {
                    vaild++;
                }
            }

            while (need.size() == vaild) {

                //找到一个子串
                if (right - left == p.length()) {
                    res.add(left);
                }
                //缩小窗口
                Character remove = s.charAt(left);
                if (need.containsKey(remove)) {

                    if (window.get(remove).equals(need.get(remove))) {
                        vaild--;
                    }

                    window.put(remove, window.getOrDefault(remove, 0) - 1);
                }
                left++;

            }

        }
        return res;

    }
} 

参考