leetcode 76 最小覆盖子串

参考资料:
https://leetcode-cn.com/problems/minimum-window-substring/solution/zui-xiao-fu-gai-zi-chuan-by-leetcode-2/

解法一:滑动窗口

直觉

本问题要求我们返回字符串SS 中包含字符串TT的全部字符的最小窗口。我们称包含TT的全部字母的窗口为可行窗口。

可以用简单的滑动窗口法来解决本问题。

在滑动窗口类型的问题中都会有两个指针。一个用于延伸现有窗口的 rightright指针,和一个用于收缩窗口的leftleft 指针。在任意时刻,只有一个指针运动,而另一个保持静止。

本题的解法很符合直觉。我们通过移动right指针不断扩张窗口。当窗口包含全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。

答案是最小的可行窗口。

举个例子,S = “ABAACBAB”,T = “ABC”。则问题答案是 “ACB” ,下图是可行窗口中的一个。
leetcode 76 最小覆盖子串_第1张图片

算法

初始,leftleft指针和rightright指针都指向SS的第一个元素.

将 rightright 指针右移,扩张窗口,直到得到一个可行窗口,亦即包含TT的全部字母的窗口。

得到可行的窗口后,将lefttleftt指针逐个右移,若得到的窗口依然可行,则更新最小窗口大小。

若窗口不再可行,则跳转至 22。
leetcode 76 最小覆盖子串_第2张图片
重复以上步骤,直到遍历完全部窗口。返回最小的窗口。
leetcode 76 最小覆盖子串_第3张图片

class Solution {
  public String minWindow(String s, String t) {

      if (s.length() == 0 || t.length() == 0) {
          return "";
      }

      // Dictionary which keeps a count of all the unique characters in t.
      Map<Character, Integer> dictT = new HashMap<Character, Integer>();
      for (int i = 0; i < t.length(); i++) {
          int count = dictT.getOrDefault(t.charAt(i), 0);
          dictT.put(t.charAt(i), count + 1);
      }

      // Number of unique characters in t, which need to be present in the desired window.
      int required = dictT.size();

      // Left and Right pointer
      int l = 0, r = 0;

      // formed is used to keep track of how many unique characters in t
      // are present in the current window in its desired frequency.
      // e.g. if t is "AABC" then the window must have two A's, one B and one C.
      // Thus formed would be = 3 when all these conditions are met.
      int formed = 0;

      // Dictionary which keeps a count of all the unique characters in the current window.
      Map<Character, Integer> windowCounts = new HashMap<Character, Integer>();

      // ans list of the form (window length, left, right)
      int[] ans = {-1, 0, 0};

      while (r < s.length()) {
          // Add one character from the right to the window
          char c = s.charAt(r);
          int count = windowCounts.getOrDefault(c, 0);
          windowCounts.put(c, count + 1);

          // If the frequency of the current character added equals to the
          // desired count in t then increment the formed count by 1.
          if (dictT.containsKey(c) && windowCounts.get(c).intValue() == dictT.get(c).intValue()) {
              formed++;
          }

          // Try and co***act the window till the point where it ceases to be 'desirable'.
          while (l <= r && formed == required) {
              c = s.charAt(l);
              // Save the smallest window until now.
              if (ans[0] == -1 || r - l + 1 < ans[0]) {
                  ans[0] = r - l + 1;
                  ans[1] = l;
                  ans[2] = r;
              }

              // The character at the position pointed by the
              // `Left` pointer is no longer a part of the window.
              windowCounts.put(c, windowCounts.get(c) - 1);
              if (dictT.containsKey(c) && windowCounts.get(c).intValue() < dictT.get(c).intValue()) {
                  formed--;
              }

              // Move the left pointer ahead, this would help to look for a new window.
              l++;
          }

          // Keep expanding the window once we are done co***acting.
          r++;   
      }

      return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
  }
}

复杂度分析

时间复杂度: O(|S| + |T|)O(∣S∣+∣T∣),其中 |S|∣S∣ 和 |T|∣T∣ 代表字符串 SS 和 TT的长度。在最坏的情况下,可能会对SS 中的每个元素遍历两遍,左指针和右指针各一遍。

空间复杂度: O(|S| + |T|)O(∣S∣+∣T∣)。当窗口大小等于|S|∣S∣时为 SS。当 |T|∣T∣ 包括全部唯一字符时为 TT 。

解法二:优化滑动窗口

直觉

对上一方法进行改进,可以将时间复杂度下降到 O(2*|filtered_S| + |S| + |T|)O(2∗∣filtered_S∣+∣S∣+∣T∣),其中 filtered_Sfiltered_S 是从SS中去除所有在TT中不存在的元素后,得到的字符串。

当 |filtered_S| <<< |S|∣filtered_S∣<<<∣S∣时,优化效果显著。这种情况可能是由于TT 的长度远远小于SS,因此SS 中包括大量TT中不存在的自负。

算法

我们建立一个 filtered_Sfiltered_S列表,其中包括 SS 中的全部字符以及它们在SS的下标,但这些字符必须在 TT中出现。

S = “ABCDDDDDDEEAFFBC” T = “ABC”
filtered_S = [(0, ‘A’), (1, ‘B’), (2, ‘C’), (11, ‘A’), (14, ‘B’), (15, ‘C’)]

此处的(0, ‘A’)表示字符’A’ 在字符串SS的下表为0。

现在我们可以在更短的字符串filtered_Sfiltered_S中使用滑动窗口法。

import javafx.util.Pair;

class Solution {
    public String minWindow(String s, String t) {

        if (s.length() == 0 || t.length() == 0) {
            return "";
        }

        Map<Character, Integer> dictT = new HashMap<Character, Integer>();

        for (int i = 0; i < t.length(); i++) {
            int count = dictT.getOrDefault(t.charAt(i), 0);
            dictT.put(t.charAt(i), count + 1);
        }

        int required = dictT.size();

        // Filter all the characters from s into a new list along with their index.
        // The filtering criteria is that the character should be present in t.
        List<Pair<Integer, Character>> filteredS = new ArrayList<Pair<Integer, Character>>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (dictT.containsKey(c)) {
                filteredS.add(new Pair<Integer, Character>(i, c));
            }
        }

        int l = 0, r = 0, formed = 0;
        Map<Character, Integer> windowCounts = new HashMap<Character, Integer>();  
        int[] ans = {-1, 0, 0};

        // Look for the characters only in the filtered list instead of entire s.
        // This helps to reduce our search.
        // Hence, we follow the sliding window approach on as small list.
        while (r < filteredS.size()) {
            char c = filteredS.get(r).getValue();
            int count = windowCounts.getOrDefault(c, 0);
            windowCounts.put(c, count + 1);

            if (dictT.containsKey(c) && windowCounts.get(c).intValue() == dictT.get(c).intValue()) {
                formed++;
            }

            // Try and co***act the window till the point where it ceases to be 'desirable'.
            while (l <= r && formed == required) {
                c = filteredS.get(l).getValue();

                // Save the smallest window until now.
                int end = filteredS.get(r).getKey();
                int start = filteredS.get(l).getKey();
                if (ans[0] == -1 || end - start + 1 < ans[0]) {
                    ans[0] = end - start + 1;
                    ans[1] = start;
                    ans[2] = end;
                }

                windowCounts.put(c, windowCounts.get(c) - 1);
                if (dictT.containsKey(c) && windowCounts.get(c).intValue() < dictT.get(c).intValue()) {
                    formed--;
                }
                l++;
            }
            r++;   
        }
        return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
    }
}


复杂度分析

时间复杂度 : O(|S| + |T|)O(∣S∣+∣T∣), 其中 |S|∣S∣ 和 |T|∣T∣ 分别代表字符串SS 和 TT的长度。 本方法时间复杂度与方法一相同,但当|filtered_S|∣filtered_S∣<<<|S|∣S∣时,复杂度会下降,因为此时迭代次数是 2*|filtered_S| + |S| + |T|2∗∣filtered_S∣+∣S∣+∣T∣。
空间复杂度 : O(|S| + |T|)O(∣S∣+∣T∣)。

总结

两次代码大部分相同,就是多了一个List。然后左右指针left,right变成指向List里面的元素。查找范围有字符串S到了List。

你可能感兴趣的:(LeetCode)