LeetCode-76.最小覆盖子串(相关话题:哈希表,双指针)

给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

说明:

  • 如果 S 中不存这样的子串,则返回空字符串 ""。
  • 如果 S 中存在这样的子串,我们保证它是唯一的答案。

解题思路:因为原题的相关话题中提示有哈希表,因此先讲一下用哈希表的解法。

先遍历待查找字符串T,统计字符串T中出现的每个字符及字符出现的次数,用HashMap

定义left、right、minLeft、minRight和count五个变量,minLeft和minRight记录最小覆盖子串的起始位置,count记录S[left, right]代表的子串中,包含的字符串T的有效字符数(比如S[left, right] = "AABD", T = "ABC",此时count = 2,因为T中只有一个A,所以"AABD"中的AA也只计一次)。

初始时,left = right = 0

  1. right往后移,直到count == T.length或者right == s.length,即S[left, right]包含T中所有字母或者字符串S遍历结束,若count == T.length,执行步骤2,若遍历到字符串S结尾,执行步骤4
  2. left往后移,去掉S[left, right]中无用的字符(如S[left, right] = "FADBAC", T = "ABC",则"F"、"A"、"D"为无效的字符,因为"F"、"D"在T中不存在,"A"虽然在T中存在,但是由于后面还有一个"A",因此"F"和"D"中的"A"也是不必要的),然后根据这个子串的长度是否比前一次找到的子串长度小来决定是否更新minLeft和minRight
  3. 将left往后移一位,即count = T.length-1,回到步骤1
  4. 返回S[minLeft, minRight]

java代码:

class Solution {
    public static String minWindow(String s, String t) {
        if("".equals(s) || "".equals(t) || s.length() < t.length())
            return "";

        Map map = new HashMap<>();
        for(int i = 0; i < t.length(); i++) {
            map.put(t.charAt(i), !map.containsKey(t.charAt(i)) ? 1 : map.get(t.charAt(i)) + 1);
        }

        int left = 0, right = 0, minLeft = 0, minRight = 0, count = 0;
        Map sMap = new HashMap<>();
        while(right < s.length()) {
            while(right < s.length() && count < t.length()) {
                char c = s.charAt(right);
                if(map.containsKey(c)) {
                    count += (map.get(c) > (!sMap.containsKey(c) ? 0 : sMap.get(c))) ? 1 : 0;
                    sMap.put(c, !sMap.containsKey(c) ? 1 : sMap.get(c) + 1);
                }
                right++;
            }

            if(right <= s.length() && count == t.length()) {
                while(left <= right && count == t.length()) {
                    char l = s.charAt(left);
                    if(map.containsKey(l)) {
                        if(map.get(l) == sMap.get(l))
                            count--;
                        sMap.put(l, sMap.get(l)-1);
                    }

                    left++;
                }

                if((0 == minLeft && minRight == minLeft) || (right-left+1) < (minRight - minLeft)) {
                    minLeft = left-1;
                    minRight = right;
                }
            }
        }

        return s.substring(minLeft, minRight);
    }
}

注:这段代码提交解答的时候,会有一个超长字符串的测试用例会超时,所以并不能通过,主要耗时在于频繁操作HashMap

在网上看了另外一个解答,不用HashMap来记录字符串T中出现的字符以及出现的次数,而改用int[]数组记录,并且用字符作为下标,可随机访问,代码如下:(执行用时8ms,超过73%java提交记录,可以感受一下带来的效率提升)

class Solution {
    public String minWindow(String s, String t) {
        if("".equals(s) || "".equals(t) || s.length() < t.length())
            return "";

        int[] tCnt = new int[256];
        for(int i = 0; i < t.length(); i++) {
            tCnt[t.charAt(i)]++;
        }

        int left = 0, right = 0, minLeft = 0, minRight = 0, count = 0;
        int[] sCnt = new int[256];
        while(right < s.length()) {
            while(right < s.length() && count < t.length()) {
                char c = s.charAt(right);
                if(0 < tCnt[c]) {
                    count += tCnt[c] > sCnt[c] ? 1 : 0;
                    sCnt[c]++;
                }
                right++;
            }

            if(right <= s.length() && count == t.length()) {
                while(left <= right && count == t.length()) {
                    char l = s.charAt(left);
                    if(0 < tCnt[l]) {
                        if(tCnt[l] == sCnt[l])
                            count--;
                        sCnt[l]--;
                    }

                    left++;
                }

                if((0 == minLeft && minRight == minLeft) || (right-left+1) < (minRight - minLeft)) {
                    minLeft = left-1;
                    minRight = right;
                }
            }
        }

        return s.substring(minLeft, minRight);
    }
}

你可能感兴趣的:(LeetCode,Java)