zoukankan      html  css  js  c++  java
  • 算法图解—最小覆盖子串

    【题目描述

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

    注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。

    示例 1:

    输入:s = "ADOBECODEBANC", t = "ABC"
    输出:"BANC"

    示例 2:

    输入:s = "a", t = "a"
    输出:"a"

    示例 3:

    输入:s = "a", t = "bb"
    输出:""
    来源:力扣(LeetCode)第76题
    链接:https://leetcode-cn.com/problems/minimum-window-substring
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    熟悉的童靴都知道,这道题属于双指针的经典题目:

    我们称之为“滑动窗口”。

    【题目分析

    我们先看看题目是什么意思。

    本问题要求我们返回字符串 s中包含字符串 的全部字符的最小窗口。

    我们称包含 t 的全部字母的窗口为「可行」窗口。

    通过示例很容易明白题目意思,只要窗口里包含有目标字符串t中的左右字符,且要求是最短的。

    那么什么是“滑动窗口”呢?

    我把它比作家中的铝合金推拉窗。

     对就是上图的这个东东。

    在滑动窗口类型的问题中都会有两个指针。一个用于「延伸」现有窗口的 right 指针(右侧),和一个用于「收缩」窗口的 left 指针(左侧)。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 s 上滑动窗口,通过移动 right 指针不断扩张窗口。当窗口包含 t 中全部所需的字符后,如果左侧能收缩,我们就收缩窗口(左侧指针  left)直到得到最小窗口。

    有人会问,为什么收缩要从左侧指针方向?

    因为,你扩张是从右侧的,当停止扩张时,你想想,为什么会停止扩张?是因为当窗口包含 t 中全部所需的字符了。所有此时最右侧的字符一定是必须的,故要从左侧缩减,如果能的话。

    【图解示例:参考leetCode】

    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/minimum-window-substring/solution/zui-xiao-fu-gai-zi-chuan-by-leetcode-solution/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    假设:

    s  = ABAACBAB
    t  =  ABC

    第一步:

    第二步:从第一步到第四步中间少了四步即是从A->B->A->A

    第三步:left 缩减至B,仍然是包含了ABC,但不知道是否是最短,只好记录下来left ,right,len = right - left。

    第四步:继续缩减;

    第五步:再次包含ABC,记录下此时的left ,right,len = len>(right - left) ? (right - left) : len。

    第六步:仍然包含ABC,记录下此时的left ,right,len = len>(right - left) ? (right - left) : len。left 继续右移。

     

    第七步:

    第八步:同上,记录,比较。不在赘述。

    第九步:

    第十步:至此后,结束。

     思路知道了,那么说一说细节:

    1、如何判断当前的窗口包含所有 t 所需的字符呢?

    这涉及到数据结构的运用了,我们知道Java中hashMap查找的时间复杂度是O(1)。

    借此,我们可以用一个需要匹配的哈希表 need<char, int> 表示 t 中所有的字符以及它们的个数,用一个窗口匹配哈希表 windows<char, int> 动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是「可行」的,即是满足“包含”的条件的。

    2、如何判断这个动态表windows中是否包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数?

    可以利用一个int 变量即可,设为kind,我称之为种类,即当windows中达到need中某字符的数量时,该变量加1。

    【代码实现】

    //C++
    class
    Solution { public: string minWindow(string s, string t) { //if(s.size() < t.size()) return ""; unordered_map<char, int> need,windows; for(char c:t) need[c]++; int kind = 0;//windows中的种类 int left = 0; int right = 0; int len = INT_MAX;//返回长度 int start = 0;//返回起始下标 while(right < s.size()){ //current char char c = s[right++]; if(need.count(c)){ windows[c]++; if(windows[c] == need[c]){ kind++; } } //if windows中种类齐全了,缩减左侧 while(kind == need.size()){ if(right - left < len){ start = left; len = right - left; } char d = s[left++]; if(need.count(d)){ windows[d]--; if(need[d] > windows[d]){ kind--; } } } } return len == INT_MAX ? "" : s.substr(start,len); } };

    【Java】

    class Solution {
        Map<Character, Integer> ori = new HashMap<Character, Integer>();
        Map<Character, Integer> cnt = new HashMap<Character, Integer>();
    
        public String minWindow(String s, String t) {
            int tLen = t.length();
            for (int i = 0; i < tLen; i++) {
                char c = t.charAt(i);
                ori.put(c, ori.getOrDefault(c, 0) + 1);
            }
            int l = 0, r = -1;
            int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
            int sLen = s.length();
            while (r < sLen) {
                ++r;
                if (r < sLen && ori.containsKey(s.charAt(r))) {
                    cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
                }
                while (check() && l <= r) {
                    if (r - l + 1 < len) {
                        len = r - l + 1;
                        ansL = l;
                        ansR = l + len;
                    }
                    if (ori.containsKey(s.charAt(l))) {
                        cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
                    }
                    ++l;
                }
            }
            return ansL == -1 ? "" : s.substring(ansL, ansR);
        }
    
        public boolean check() {
            Iterator iter = ori.entrySet().iterator(); 
            while (iter.hasNext()) { 
                Map.Entry entry = (Map.Entry) iter.next(); 
                Character key = (Character) entry.getKey(); 
                Integer val = (Integer) entry.getValue(); 
                if (cnt.getOrDefault(key, 0) < val) {
                    return false;
                }
            } 
            return true;
        }
    }

    【复杂度分析】

    • 时间复杂度:最坏情况下左右指针对 s 的每个元素各遍历一遍,哈希表中对 s 中的每个元素各插入、删除一次,对 t 中的元素各插入一次。每次检查是否可行会遍历整个 t 的哈希表,哈希表的大小与字符集的大小有关,设字符集大小为 C,则渐进时间复杂度为 O(C⋅∣s∣+∣t∣)。
    • 空间复杂度:这里用了两张哈希表作为辅助空间,每张哈希表最多不会存放超过字符集大小的键值对,我们设字符集大小为 C ,则渐进空间复杂度为 O(C)。

    Over...

  • 相关阅读:
    相关系数的元分析,以及带调节变量的相关系数的元分析(R)
    共有地址网段类别的划分,几个特殊的私有地址,关于子网掩码,网关的小知识 (网络)
    在文件内夹内部建立子文件夹(python)(os)
    晶振, 机器周期,进位 (单片机)
    数码管动态显示,显示从1到9,每一位显示一个数字 (单片机)
    比较R平方的差值,比较两个回归方程的(R)
    react项目控制台报错data.slice.is not function
    useRef源码
    useReducer源码实现
    useContext源码解读
  • 原文地址:https://www.cnblogs.com/gjmhome/p/14253780.html
Copyright © 2011-2022 走看看