题目链接

LeetCode

题目描述

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

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入: s = “ADOBECODEBANC”, t = “ABC”
输出: “BANC”

示例 2:

输入: s = “a”, t = “a”
输出: “a”

示例 3:

输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符’a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

  • 1 <= s.length, t.length <= 105
  • st 由英文字母组成

进阶: 你能设计一个在 o(n) 时间内解决此问题的算法吗?

解题思路

方法一:滑动窗口

我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。在任意时刻,只有一个指针运动,而另一个保持静止。
我们在 s 上滑动窗口,通过移动 r 指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。
76. 最小覆盖子串** - 图1
如何判断当前的窗口包含所有 t 所需的字符呢?我们可以用一个哈希表表示 t 中所有的字符以及它们的个数,用一个哈希表动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是「可行」的。

  1. class Solution {
  2. public:
  3. string minWindow(string s, string t) {
  4. int len1 = s.length();
  5. int len2 = t.length();
  6. if(len1==0||len2==0||len1<len2){
  7. return "";
  8. }
  9. int minLen = INT_MAX;
  10. int left = 0;
  11. int right = 0;
  12. int distance = 0;
  13. int begin = 0;
  14. for(const char& c : t){
  15. tFreq[c]++;
  16. }
  17. while(right<len1){
  18. if(tFreq[s[right]]==0){
  19. right++;
  20. continue;
  21. }
  22. if(winFreq[s[right]]<tFreq[s[right]]){
  23. distance++;
  24. }
  25. winFreq[s[right]]++;
  26. right++;
  27. while(distance==len2){
  28. if(right-left < minLen){
  29. minLen = right - left;
  30. begin = left;
  31. }
  32. if(tFreq[s[left]]==0){
  33. left++;
  34. continue;
  35. }
  36. if(winFreq[s[left]] == tFreq[s[left]]){
  37. distance--;
  38. }
  39. winFreq[s[left]]--;
  40. left++;
  41. }
  42. }
  43. if(minLen == INT_MAX){
  44. return "";
  45. }
  46. return s.substr(begin,minLen);
  47. }
  48. private:
  49. unordered_map<char,int> winFreq;
  50. unordered_map<char,int> tFreq;
  51. };
  1. public class Solution {
  2. public String minWindow(String s, String t) {
  3. int sLen = s.length();
  4. int tLen = t.length();
  5. if(sLen==0||tLen==0||sLen<tLen){
  6. return "";
  7. }
  8. char[] charArrayS = s.toCharArray();
  9. char[] charArrayT = t.toCharArray();
  10. int[] winFreq = new int[128];
  11. int[] tFreq = new int[128];
  12. for (char c : charArrayT){
  13. tFreq[c]++;
  14. }
  15. int distance = 0;
  16. int minLen = Integer.MAX_VALUE;
  17. int begin = 0;
  18. int left = 0;
  19. int right = 0;
  20. while(right<sLen){
  21. // 如果t中没有当前元素,跳过
  22. if (tFreq[charArrayS[right]]==0){
  23. right++;
  24. continue;
  25. }
  26. // 如果当前窗口中当前元素的个数小于t中的个数,distance加一
  27. if(winFreq[charArrayS[right]] < tFreq[charArrayS[right]]){
  28. distance++;
  29. }
  30. winFreq[charArrayS[right]]++;
  31. right++;
  32. // 窗口中包含t中所有的元素
  33. while (distance == tLen){
  34. if (right-left < minLen){
  35. minLen = right - left;
  36. begin = left;
  37. }
  38. if (tFreq[charArrayS[left]]==0){
  39. left++;
  40. continue;
  41. }
  42. if(winFreq[charArrayS[left]] == tFreq[charArrayS[left]]){
  43. distance--;
  44. }
  45. winFreq[charArrayS[left]]--;
  46. left++;
  47. }
  48. }
  49. if (minLen == Integer.MAX_VALUE){
  50. return "";
  51. }
  52. return s.substring(begin,begin+minLen);
  53. }
  54. }
  1. class Solution {
  2. Map<Character, Integer> oriMap = new HashMap<>();
  3. Map<Character, Integer> cntMap = new HashMap<>();
  4. public String minWindow(String s, String t) {
  5. for(int i = 0; i < t.length(); ++i){
  6. oriMap.put(t.charAt(i) , oriMap.getOrDefault(t.charAt(i), 0) + 1);
  7. }
  8. int len = Integer.MAX_VALUE;
  9. int L = 0, R = 0;
  10. int ansL = 0, ansR = 0;
  11. while(R < s.length()){
  12. // 如果当前字符属于t,
  13. if(R < s.length() && oriMap.containsKey(s.charAt(R))){
  14. cntMap.put(s.charAt(R) , cntMap.getOrDefault(s.charAt(R), 0) + 1);
  15. }
  16. // 左指针右移
  17. while(check() && L <= R){
  18. if(R - L + 1 <len){
  19. len = R - L + 1;
  20. ansL = L;
  21. ansR = L + len;
  22. }
  23. if(oriMap.containsKey(s.charAt(L))){
  24. cntMap.put(s.charAt(L) , cntMap.getOrDefault(s.charAt(L), 0) - 1);
  25. }
  26. ++L;
  27. }
  28. ++R;
  29. }
  30. return ansR == 0 ? "" : s.substring(ansL, ansR);
  31. }
  32. private boolean check(){
  33. for(Map.Entry<Character, Integer> entry : oriMap.entrySet()){
  34. if(cntMap.getOrDefault(entry.getKey(), 0) < entry.getValue()){
  35. return false;
  36. }
  37. }
  38. return true;
  39. }
  40. }
  • 时间复杂度 O(C⋅∣s∣+∣t∣) 最坏情况下左右指针对 s 的每个元素各遍历一遍,哈希表中对 s 中的每个元素各插入、删除一次,对 t 中的元素各插入一次。每次检查是否可行会遍历整个 t 的哈希表,哈希表的大小与字符集的大小有关,设字符集大小为 C,则渐进时间复杂度为 O(C⋅∣s∣+∣t∣)。
  • 空间复杂度 O(C):这里用了两张哈希表作为辅助空间,每张哈希表最多不会存放超过字符集大小的键值对,我们设字符集大小为 C ,则渐进空间复杂度为 O(C)。