覆盖子串

Posted by 令德湖周杰伦 on 05-22,2024

1. 覆盖子串

子字符串: 是字符串中连续的 非空 字符序列
覆盖子串:和子序列的区别为,覆盖子串不要求顺序,即ADCB的子串有:ABC、ABD等, 而子序列有:ADC, ADB等

2. 思路

由于覆盖子串不再考虑顺序性,一般都会使用Map来记录字符和字符频次,以此来判断子串的合法性

3. 题目

3.1 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

3.1.1 思路

暴力解法就是:

  1. 2个指针i,j去遍历s,取s[i-j]上的字符去和t对比O(n^2)
  2. 如果 s[i-j]中有t,那么记录最小长度
  3. 如果 s[i-j]中不包含t,直接跳过
  4. s[i-j]中 t 有没有,也是一个O(n)的时间复杂度
  5. 时间整体复杂度:O(n^3)

正常解法:遍历一次,滑动窗口 + 记忆化

  1. 使用map1记录t中每个字符出现的次数
  2. 使用windowMap记录,s上的窗口内 & 有t的目标字符出现的次数,在t中不存在的无效的字符不记录
  3. 遍历s,检查窗口内map是否覆盖t
    1. 如果符合,记录minLegth,minStartIndex,窗口缩小,left++,windowMap更新
    2. 如果不符合,扩窗口,right++
  4. 最后返回: s.substring(minStartIndex, minStartIndex + minLength) 即可

时间复杂度: 由于遍历O(n),检查窗口内map是否覆盖t也是O(n),整体O(n^2)

 public String minWindow(String s, String t) {
        //记录t中字符出现的次数
        Map<Character, Integer> map1 = new HashMap<>();
        //动态窗口记录 有目标字符 以及 次数
        Map<Character, Integer> windowMap = new HashMap<>();
        int l = 0;
        int r= 0;
        int minLength = Integer.MAX_VALUE;
        int minStartIndex = 0;
        //构建map1
        for (char c : t.toCharArray()) {
            map1.put(c, map1.getOrDefault(c, 0) + 1);
        }
        char[] sArr = s.toCharArray();
        while (r < s.length()){
            char currentChar = sArr[r];
            //有目标字符的才加入
            if(map1.get(currentChar)!=null){
                windowMap.put(currentChar, windowMap.getOrDefault(currentChar, 0)+1);
            }
            if(r - l + 1 < t.length()){
                //窗口不够长度,扩,r++
                r++;
                continue;
            }
            //去检查
            boolean check = checkWindow(windowMap, map1, t);
            if(check){
                if(r-l+1 < minLength){
                    minLength = r-l+1;
                    minStartIndex = l;
                }
                //尝试缩小,更新窗口统计
                while (r - l + 1 > t.length()){
                    char tmpLeftChar = s.charAt(l);
                    //如果当前的字符在winds中,窗口缩小,对应的次数需要减1
                    if(map1.get(tmpLeftChar)!=null){
                        windowMap.put(tmpLeftChar, windowMap.get(tmpLeftChar)-1);
                    }
                    l++;
                    boolean checkWindow = checkWindow(windowMap, map1, t);
                    if(checkWindow){
                        if(r-l+1 < minLength){
                            minLength = r-l+1;
                            minStartIndex = l;
                        }
                    }else {
                        break;
                    }
                }
                r++;
            }else {
                //不符合,那么扩窗口,r++
                r++;
            }

        }
        return minLength == Integer.MAX_VALUE ? "" : s.substring(minStartIndex, minStartIndex + minLength);
    }


    private boolean checkWindow(Map<Character, Integer> windowMap, Map<Character, Integer> map1, String t){
        for (char c : t.toCharArray()) {
            Integer windowsExistCount = windowMap.get(c);
            Integer count = map1.get(c);
            if(windowsExistCount == null){
                return false;
            }
            if(windowsExistCount < count){
                return false;
            }

        }
        return true;
    }

3.1.2 优化解法

设t=ABC
优化解法:遍历一次,滑动窗口 + 记忆化 + 检查优化

  1. 使用map1记录t中每个字符出现的次数
  2. 使用windowMap记录,s上的窗口内 & 有t的目标字符出现的次数,在t中不存在的无效的字符不记录,同时使用windowCount,记录窗口最小有效字符数,不会重复加入,即窗口[ABBC],那么windowCount=3,而不是4
  3. 遍历s,检查窗口内map是否覆盖t(判断windowsCount>=map1.size()即可)
    1. 如果符合,记录minLegth,minStartIndex,窗口缩小,left++,windowMap更新
    2. 如果不符合,扩窗口,right++
  4. 最后返回: s.substring(minStartIndex, minStartIndex + minLength) 即可

时间复杂度: 遍历O(n)

代码如下:

public String minWindowUpgrade(String s, String t) {
        //记录t中字符出现的次数
        Map<Character, Integer> map1 = new HashMap<>();
        //动态窗口记录 有目标字符 以及 次数
        Map<Character, Integer> windowMap = new HashMap<>();
        int l = 0;
        int r= 0;
        int minLength = Integer.MAX_VALUE;
        int minStartIndex = 0;
        int wincount=0;
        //构建map1
        for (char c : t.toCharArray()) {
            map1.put(c, map1.getOrDefault(c, 0) + 1);
        }
        char[] sArr = s.toCharArray();
        while (r < s.length()){
            char currentChar = sArr[r];
            //有目标字符的才加入
            if(map1.get(currentChar)!=null){
                windowMap.put(currentChar, windowMap.getOrDefault(currentChar, 0)+1);
                if(map1.get(currentChar).intValue() == windowMap.get(currentChar)){
                    wincount++;
                }

            }
            if(r - l + 1 < t.length()){
                //窗口不够长度,扩,r++
                r++;
                continue;
            }
            //去检查
            boolean check = checkWindowUpgrade(wincount, map1.size());
            if(check){
                if(r-l+1 < minLength){
                    minLength = r-l+1;
                    minStartIndex = l;
                }
                //尝试缩小,更新窗口统计
                while (wincount ==  map1.size()){
                    char tmpLeftChar = s.charAt(l);
                    //如果当前的字符在winds中,窗口缩小,对应的次数需要减1
                    if(map1.get(tmpLeftChar)!=null){
                        if(map1.get(tmpLeftChar).intValue() == windowMap.get(tmpLeftChar)){
                            wincount--;
                        }
                        windowMap.put(tmpLeftChar, windowMap.get(tmpLeftChar)-1);
                    }
                    l++;
                    boolean checkWindow = checkWindowUpgrade(wincount, map1.size());
                    if(checkWindow){
                        if(r-l+1 < minLength){
                            minLength = r-l+1;
                            minStartIndex = l;
                        }
                    }else {
                        break;
                    }
                }
                r++;
            }else {
                //不符合,那么扩窗口,r++
                r++;
            }

        }
        return minLength == Integer.MAX_VALUE ? "" : s.substring(minStartIndex, minStartIndex + minLength);
    }

    private boolean checkWindowUpgrade(int windowsCount, int tCount){
        return windowsCount>=tCount;
    }