题目

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

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

示例 1:

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

示例 2:

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

提示:

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

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

https://leetcode-cn.com/problems/minimum-window-substring/

方法

滑动窗口思想

在滑动窗口类型的问题中都会有两个指针。一个用于「延伸」现有窗口的 r 一个用于「收缩」窗口的 l 指针。在任意时刻,只有一个指针运动,而另一个保持静止。在 s 上滑动窗口,移动指针 r 扩大窗口直到能容纳所有t的字符,停止扩张收缩 l 指针,如果能收缩就收缩直到 l 达到最小窗口。

示意图
示意图

解题思路

这里的难点主要在两个,如何实现窗口,另一个是如何判断划定的窗口里包含了所有的目标字符串。

我原本的第一种思路,做两次遍历第一次窗口遍历第二次窗口移动遍历,这种方式时间复杂度太高,直接就会超时也不符合题干的要求。

第二次看了题解思路,我知道了原来是右边扩张一下左边再收缩的方式移动窗口,就设计了切割字符串的格式,但是字符串频繁切割会导致性能问题,在执行大字符串操作出现了超时,大概要3秒,只能再换一种。

第三种,用哈希表来存储字符串和判断,最终时间确实快了相当多!

定义两个Map<字符,字符数量>,目标字符串和窗口中符合的字符串。

Map<Character, Integer> originMap = new HashMap<>(171);
Map<Character, Integer> windowsMap = new HashMap<>(171);

1.移动窗口的右边的节点,直到满足字符串在target里。这里有个隐藏条件,我们最后一个字符一定是在Target字符串里,所以我们在扫描的时候可以快速忽略掉不在Target里的字符串。
2.当满足字符串在target里,这时候移动左边节点,直到窗口里的字符串不满足target。同样有个隐藏条件,左边的字符一定是在Target里。
3.在满足条件的字符进入或者出去的时候要注意刷新windowsMap
4.我定义了一个Map用于存储所有满足的target的字符串,最后再过滤出最小的

完整代码

package org.chenly.study;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

/**
 * https://leetcode-cn.com/problems/minimum-window-substring/
 *
 * @author chenly
 * @create 2020-11-20 22:01
 */
public class MinWindowSubstring76 {
   public static String minWindow(String s, String t) {
      if (s.length() < t.length()) {
         return "";
      }
      Map<Character, Integer> originMap = new HashMap<>(171);
      Map<Character, Integer> windowsMap = new HashMap<>(171);

      for (char c : t.toCharArray()) {
         originMap.compute(c, add());
      }

      Map<Integer, String> integerStringMap = new HashMap<>();

      int left = 0;
      int right = 0;
      String windows = "";
      while (right < s.length()) {
         char rightChar = s.charAt(right);
         //扫到满足的字符
         if (originMap.containsKey(rightChar)) {
            windowsMap.compute(rightChar, add());

            while (equal(windowsMap, originMap) && left <= right) {
                //将符合的字符串记录在一个新map里
               windows = s.substring(left, right + 1);
               integerStringMap.put(windows.length(), windows);

               char leftChar = s.charAt(left);
               //扫到满足的字符
               if (originMap.containsKey(leftChar)) {
                  windowsMap.compute(leftChar, sub());

               }
               left++;
            }

         }
         right++;

      }
      return integerStringMap.entrySet()
            .stream()
            .min(Map.Entry.comparingByKey())
            .map(Map.Entry::getValue)
            .orElse("");
   }

   private static BiFunction<Character, Integer, Integer> sub() {
      return (character, integer) -> {
         if (integer == null) {
            return 0;
         }
         return --integer;
      };
   }

   private static BiFunction<Character, Integer, Integer> add() {
      return (character, integer) -> {
         if (integer == null) {
            return 1;
         }
         return ++integer;
      };
   }

   private static boolean equal(Map<Character, Integer> windowsMap, Map<Character, Integer> originMap) {
      return originMap.entrySet()
            .stream()
            .allMatch(entry -> windowsMap.getOrDefault(entry.getKey(), 0) >= entry.getValue());

   }

   public static void main(String[] args) {

      System.out.println(minWindow(
            "ADOBECODEBANC",
            "ABC"));

   }
}

内存占用优化

用于存储结果的map很显然有点多余,所以想办法去掉它

public static String minWindow(String s, String t) {
   if (s.length() < t.length()) {
      return "";
   }
   Map<Character, Integer> originMap = new HashMap<>();
   Map<Character, Integer> windowsMap = new HashMap<>();

   for (char c : t.toCharArray()) {
      originMap.compute(c, add());
   }

   int left = 0;
   int right = 0;
   String windows = "";
   int min = Integer.MAX_VALUE;
   while (right < s.length()) {
      char rightChar = s.charAt(right);
      if (originMap.containsKey(rightChar)) {
         windowsMap.compute(rightChar, add());

         while (equal(windowsMap, originMap) && left <= right) {

            if (right - left + 1 < min) {
               windows = s.substring(left, right + 1);
               min = right - left + 1;
            }
            char leftChar = s.charAt(left);
            if (originMap.containsKey(leftChar)) {
               windowsMap.compute(leftChar, sub());

            }
            left++;
         }

      }
      right++;

   }
   return windows;
}

失败方法

 //复杂度不符合 超时
   /* public static String minWindow(String s, String t) {
         if(s.length()<t.length()){
            return "";
         }
         int windowsLength = t.length();
         while (windowsLength<=s.length()){
            String windows = "";
            int left = 0;
            int right = windowsLength;
            while (windows.length() <= s.length()) {

               windows = s.substring(left, right);
               if (equals(windows, t)) {
                  return windows;
               } else {
                  left = left + 1;
                  right = right + 1;
                  if (right > s.length()) {
                     break;
                  }
               }

            }
            windowsLength++;

         }

         return "";

      }*/

   //字符串过长 超时
   /*public static String minWindow1(String s, String t) {
      if (s.length() < t.length()) {
         return "";
      }
      Map<Integer, String> integerStringMap = new HashMap<>();

      String windows = "";

      int left = 0;
      int right = t.length();
      while (right <= s.length()) {
         windows = s.substring(left, right);
         if (equals(windows, t)) {
            left++;
            integerStringMap.put(windows.length(), windows);
         } else {
            right++;
         }
      }

      return integerStringMap.entrySet()
            .stream()
            .min(Map.Entry.comparingByKey())
            .map(Map.Entry::getValue)
            .orElse("");

   }

   private static boolean equals(String temp, String t) {
      char[] chars1 = t.toCharArray();
      int a = 0;
      for (char c : chars1) {
         if (temp.indexOf(c) != -1) {
            a++;
            temp = temp.substring(0, temp.indexOf(c)) + temp.substring(temp.indexOf(c) + 1);
         }
      }
      return a == t.length();
   }*/