最小覆盖子串

121 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

76. 最小覆盖子串

题目描述

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

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

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

  • 1 <= s.length, t.length <= 105

  • s 和 t 由英文字母组成 示例 1:

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

示例 2:

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

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

思路分析

  • 这也是 leetcode 的一道 hard 题目,仔细阅读题目后发现这并不难想到思路,而是难在如何处理细节。如果在这使用暴力法的话,显然不符合题目的考察意向,所以要用其他方法。
  • 当遇到一个字符串是否是另一个字符串的子串时,我们一般都会想到用滑动窗口。但是本题得难点在于用什么数据结构来维护滑动窗口。为了降低代码得操作复杂性,我们可以用哈希表来维护滑动得窗口,同时为了保证程序得性能,所以用数组来模拟哈希表,具体见 AC 代码。易知,当 ss 长度为 mmtt 长度为 nn 时,时间复杂度为 O(m+n)O(m + n)

l.webp

AC 代码

class Solution {
    public String minWindow(String s, String t) {
        char[] cs = s.toCharArray();
        char[] ct = t.toCharArray();
        int n = cs.length;
        // 如果 s 的长度小于 t 的长度,那么 s 中肯定不存在涵盖 t 所有字符的子串.
        if (n < ct.length) return "";
        // 统计 t 字符串的字符.
        int[] tMap = new int['z' - 'A' + 1];
        // 统计滑动窗口的字符,用于匹配 t 字符串.
        int[] sMap = new int['z' - 'A' + 1];
        // 记录当前在窗口内是否还欠缺某个字符才使得窗口字符匹配 t 字符.
        Set<Integer> set = new HashSet<>();
        for (char c : ct) {
            int index = c - 'A';
            set.add(index);
            tMap[index]++;
        }
        String res = "";
        // 记录窗口得最小长度.
        int min = 100000;
        // 窗口左边界.
        int left = 0;
        for (int i = 0; i < n; i++) {
            int index = cs[i] - 'A';
            sMap[index]++;
            if (sMap[index] >= tMap[index]) {
                set.remove(index);
            }
            if (set.size() != 0) continue;
            // 在窗口包含 t 字符串时,是否能继续缩短字符长度.
            while (left < i && (tMap[cs[left] - 'A'] == 0 || 
                    (tMap[cs[left] - 'A'] != 0 && sMap[cs[left] - 'A'] > tMap[cs[left] - 'A']))) {
                sMap[cs[left++] - 'A']--;
            }
            if (i - left + 1 < min) {
                res = s.substring(left,i + 1);
                min = i - left + 1;
            }
            set.add(cs[left] - 'A');
            sMap[cs[left++] - 'A']--;
        }
        return res;
    }
}

总结

  • 滑动窗口题型虽然很常见,但是遇到它得各种变种题时还是要多加注意的,不然的话就很容易栽倒在细节坑上,平时在练习时也要多用几种数据结构去解决问题,才能够应付更多的变型题。