GuilinDev

Lc0076

05 August 2008

76 Minimum Window Substring

原题概述

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

Example:

1
2
Input: S = "ADOBECODEBANC", T = "ABC"
Output: "BANC"

Note:

  • If there is no such window in S that covers all characters in T, return the empty string
    1
    
    ""
    
    .
  • If there is such window, you are guaranteed that there will always be only one unique minimum window in S.

题意和分析

要求在字符串s中找到最小的子字符串w(window),其中包含T中所有的字符,如果没有就返回空字符串,如果有,最小的字符串只有唯一的一个。

代码

Labuladuodong的双指针模板,用两个HashMap,比较好理解的做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Solution {
    public String minWindow(String s, String t) {
        HashMap<Character, Integer> needs = new HashMap<>(); // target - t中字符的出现次数
        HashMap<Character, Integer> window = new HashMap<>(); // 窗口中相应“有效”字符的出现次数
        
        for (char ch : t.toCharArray()) {
            needs.put(ch, needs.getOrDefault(ch, 0) + 1);
        }
        
        int left = 0, right = 0; // 窗口的左右边界初始为零[left, right)
        int valid = 0; // 窗口中满足在needs的字符个数
        
        // 记录最小覆盖字串的起始索引及长度
        int start = 0, len = Integer.MAX_VALUE;
        while (right < s.length()) {
            // ch是准备进入窗口的字符
            char ch = s.charAt(right);
            // 得到ch后右移窗口,相当于寻找一个【可行解】
            right++;
            
            // 更新窗口内的一系列数据
            if (needs.containsKey(ch)) {//target-t中需要该字符
                window.put(ch, window.getOrDefault(ch, 0) + 1);
                
                // 该字符在window中的数量已经等于(满足)了target-t中该字符
                if (window.get(ch).equals(needs.get(ch))) { 
                    valid++;
                }
            }
            
            // 判断窗口左侧是否需要收紧,以此来优化【可行解】,得到最优解
            // valid已经达到target中所有字符的数量,表示找到一个s中的子字符串包含全部t中字符
            // 注意这里是hashmap的size而不是t的leng,因为可能有重复元素
            while(valid == needs.size()) { 
                //更新最小覆盖字串
                if (right - left < len) { //遇到更小的字串
                    start = left;
                    len = right - left;
                }
                // 准备移除窗口的字符
                char de = s.charAt(left);
                // 移动窗口左侧缩小窗口
                left++;
                
                // 更新窗口内的一系列数据,窗口左边遇到一个“有效”字符
                if (needs.containsKey(de)) {
                    if (window.get(de).equals(needs.get(de))) {
                        valid--;
                    }
                    window.put(de, window.get(de) - 1); // 该有效字符减少一次
                }
            }
        }
        // 返回最小覆盖字串
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

使用HashMap和Deque

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class Solution {
    public String minWindow(String s, String t) {
        
        // By default the window is the entire string...
        int bestMin = 0;
        int bestMax = s.length() -1;
        
        // This holds the amount of each character we need to see
        Map<Character, Integer> charCount = new HashMap<>();
        
        for (int i = 0; i < t.length(); i++) {
            char c = t.charAt(i);
            Integer count = charCount.getOrDefault(c, 0);
            charCount.put(c, count+1);
        }
        
        // This holds characters and their last seen indexes in the string
        Map<Character, Deque<Integer>> lastSeen = new HashMap<>();
        // This holds characters that we have seen the minimum amount of
        Set<Character> seenMin = new HashSet<>();
        
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i); 
            
            // check if this is a character we are looking for
            if (charCount.containsKey(c)) {
                
                Deque<Integer> positions = lastSeen.getOrDefault(c, new LinkedList<>());
                
                // If we have stored the maximum positions, remove the earliest and add the newest to the end
                if (positions.size() == charCount.get(c)) {
                    positions.removeFirst();
                }      
                
                positions.addLast(i);
                
                if (positions.size() == charCount.get(c)) {
                    seenMin.add(c);
                }
                
                lastSeen.put(c, positions);
                
                // If we have seen all our characters the minimum amount of times
                // we can try and calculate the minimum total length that holds all those characters
                if (seenMin.size() == charCount.size()) {
                    int min = Integer.MAX_VALUE;
                    int max = Integer.MIN_VALUE;
                    
                    // Iterate over the positions for each character and grab their min/max positions
                    for (Deque<Integer> values : lastSeen.values()) {
                        min = Math.min(min, values.getFirst());
                        max = Math.max(max, values.getLast());
                    }  
                    
                    // If we have a smaller length than our current best length, save it.
                    if (max - min < bestMax - bestMin) {
                        bestMax = max;
                        bestMin = min;
                    }
                }
            }      
        }
        
        // If we have seen all of our characters the minimum amount of times we know we will have a valid solution
        if (seenMin.size() == charCount.size()) {
            return s.substring(bestMin, bestMax+1);  
        } else {
            return "";
        }
    }
}