力扣面试150题--最小覆盖子串

Day 18

题目描述

力扣面试150题--最小覆盖子串_第1张图片

思路

初次思路:利用滑动窗口,做法如下:

  1. 创建一个hashmap用于存放子串中每个字符出现的次数
  2. 向前遍历主串s
  3. 遍历中,该字符如果存在于map中,有以下两种情况:
  4. 该字符在map中计数是大于0的,说明这是匹配上了,那么将map中对应字符计数减一,计数器sum++,判断假如sum=1,说明这是第一个匹配上的点,记录它的起始位置为beg,如果sum==2并且next!=0说明,这个是第二个匹配上的点,记录下它的位置next,同时判断sum=t,length()(说明这是一个完整的符合条件的字串),比较与之前的长度,如果为更小的,更新起始点beg1=beg和end=i,清空计数器,恢复map,如果next存在,下一个遍历点变为next+1。
  5. 如果这个字符在map中计数器等于0 ,也可以做下一个起始点看,如果sum=1,将next指向这个点的序号。
  6. 如果这个字符并没有匹配上,什么也不用做继续向前遍历即可。
  7. 如果end!=0说明至少有一个子串符合条件,就拼接beg1和end,返回答案
  8. 如果end=0,说明并没有子串符合条件,返回nuill。
class Solution {
    public  String minWindow(String s, String t) {
        String res="";
        int beg=0,next=0,sum=0;
        int max=0;//用于判断长度是否为最小的
        int beg1=0, end=0;//记录最后的拼接范围
        HashMap<Character, Integer> map1 = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {//初始化map
            if (map1.containsKey(t.charAt(i))){
                map1.put(t.charAt(i),map1.get(t.charAt(i))+1);
            }
            else{
                map1.put(t.charAt(i),1);
            }
        }
        HashMap<Character, Integer> map =(HashMap<Character,Integer>)map1.clone();//克隆,免得修改值
        for(int i=0;i<s.length();i++){//向前遍历s字符串
            if(map.containsKey(s.charAt(i))){//存在于map中的字符
                if(map.get(s.charAt(i))>0) {//符合条件的字符
                    sum++;//计数器加1
                    map.put(s.charAt(i), map.get(s.charAt(i)) - 1);//map中数量-1
                    if (sum == 1) {//记录为起始点
                        beg = i;
                    }
                    if (sum == 2 && next == 0) {
                    //如果next!=0说明这个点之前已经存在next了,
                        next = i;
                    }
                    if (sum == t.length()) {//满足条件的字串
                        if (max == 0 || i + 1 - beg < max) {//判断长度
                            max = i + 1 - beg;
                            beg1 = beg;
                            end = i + 1;//更新拼接范围
                        }
                        sum = 0;//清空计数器
                        if (next != 0) {//下一轮遍历到next
                            i = next - 1;
                        } else {//假如只有一个字符的子串,则向后遍历即可
                            i = beg;
                        }
                        next = 0;//清空next
                        map = (HashMap<Character, Integer>) map1.clone();
                        //重置map
                    }
                }
                else{//如果一个字符在map中,但对应值为0,也可能作为下一个起始点
                    if (sum==1){
                        next=i;
                    }
                }
            }
        }
        if(end!=0){
            return s.substring(beg1,end);
        }
        return res;
    }
}

问题:超时了 很可惜。
力扣面试150题--最小覆盖子串_第2张图片
优化思路:初次思路做法是这样的(磨了一下午没做出来 以后二刷回来更新
s = “ADOBECODEBANC”, t = “ABC”
第一次得到的子串是:ADOBEC
下一轮遍历的起始点是:B 得到的字符串为BECODEBA
下一轮遍历的起始点是:C 得到的子串为:CODEBA
下一轮遍历的起始点是: B 得到的子串为:BANC
下一轮遍历的起始点是:A 得到的子串为ANC 结束
那存在什么问题导致了时间运行过长呢?
看了题解后发现 :每一轮取得的起始点,都是上一次已经遍历过的值,如果接着向后进行遍历,是一个重复的过程。那我们应该怎么做呢
假如第一轮取出的起始点为B,那么ADOBEC就会变成BEC,对于新的字符串而言,是不是我们向后再找到一个A就可以了。由此产生了以下做法。

  1. 使用两个指针 left 和 right 来表示滑动窗口的左右边界。
  2. right 指针向右移动,不断扩大窗口,将字符加入窗口并更新窗口内字符的频率。
  3. 当窗口内包含了 t 中所有字符时,尝试移动 left 指针缩小窗口,同时更新最小子串的长度和位置。(豆包的答案)

    // 此函数用于在字符串 s 中找到涵盖字符串 t 所有字符的最小子串
    public static String minWindow(String s, String t) {
        // 若 s 或 t 为 null,或者 s 的长度为 0,或者 t 的长度为 0,或者 s 的长度小于 t 的长度
        // 则不可能存在涵盖 t 所有字符的子串,直接返回空字符串
        if (s == null || t == null || s.length() == 0 || t.length() == 0 || s.length() < t.length()) {
            return "";
        }

        // 用于统计字符串 t 中每个字符的出现次数
        Map<Character, Integer> target = new HashMap<>();
        // 遍历字符串 t 中的每个字符
        for (char c : t.toCharArray()) {
            // 若字符 c 已在 target 中,将其对应的值加 1;若不在,初始化为 1
            target.put(c, target.getOrDefault(c, 0) + 1);
        }

        // 记录 t 中不同字符的种类数
        int required = target.size();
        // 滑动窗口的左边界
        int left = 0;
        // 滑动窗口的右边界
        int right = 0;
        // 记录当前窗口中已经满足 t 中字符数量要求的字符种类数
        int formed = 0;
        // 用于统计当前滑动窗口中每个字符的出现次数
        Map<Character, Integer> windowCounts = new HashMap<>();
        // 用于记录最小子串的信息,ans[0] 记录最小子串的长度,ans[1] 记录最小子串的左边界,ans[2] 记录最小子串的右边界
        int[] ans = {-1, 0, 0};

        // 开始移动右边界,扩大窗口
        while (right < s.length()) {
            // 获取当前右边界指向的字符
            char c = s.charAt(right);
            // 将该字符加入窗口,并更新其在窗口中的出现次数
            windowCounts.put(c, windowCounts.getOrDefault(c, 0) + 1);

            // 若该字符是 t 中的字符,且在窗口中的出现次数达到了 t 中该字符的出现次数
            if (target.containsKey(c) && windowCounts.get(c).intValue() == target.get(c).intValue()) {
                // 满足要求的字符种类数加 1
                formed++;
            }

            // 当窗口中已经包含了 t 中所有字符时,尝试缩小窗口
            while (left <= right && formed == required) {
                // 获取当前左边界指向的字符
                c = s.charAt(left);

                // 若当前是第一次找到符合条件的子串,或者当前子串的长度比之前记录的最小子串长度更短
                if (ans[0] == -1 || right - left + 1 < ans[0]) {
                    // 更新最小子串的长度
                    ans[0] = right - left + 1;
                    // 更新最小子串的左边界
                    ans[1] = left;
                    // 更新最小子串的右边界
                    ans[2] = right;
                }

                // 将左边界字符从窗口中移除,并更新其在窗口中的出现次数
                windowCounts.put(c, windowCounts.get(c) - 1);
                // 若移除该字符后,该字符在窗口中的出现次数小于 t 中该字符的出现次数
                if (target.containsKey(c) && windowCounts.get(c).intValue() < target.get(c).intValue()) {
                    // 满足要求的字符种类数减 1
                    formed--;
                }

                // 左边界右移,缩小窗口
                left++;
            }

            // 右边界右移,扩大窗口
            right++;
        }

        // 若最终没有找到符合条件的子串,ans[0] 仍为 -1,返回空字符串;否则返回最小子串
        return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
    }

你可能感兴趣的:(leetcode,面试,java)