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

117 篇文章 0 订阅

给定一个字符串 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<Character, Integer> 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<Character, Integer> 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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值