LeetCode | 76 | 最小覆盖子串 | 双指针 | 滑动窗口 | 哈希集合 | 字符串 | 困难?!| Java | 详细注释

🙋大家好!我是毛毛张!
🌈个人首页: 神马都会亿点点的毛毛张
这道题在LeetCode上的难度标签为:困难!你能看出来这是一道用双指针就可以解决的题目吗?

LeetCode链接:76. 最小覆盖子串

1.题目描述

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

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

示例 3:

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

提示:

  • m == s.length
  • n == t.length
  • 1 < = m , n < = 1 0 5 1 <= m, n <= 10^5 1<=m,n<=105
  • st 由英文字母组成

进阶: 你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

2.题解

2.1 暴力解法(超时)

import java.util.HashMap;
import java.util.Map;

class Solution {
    public String minWindow(String s, String t) {
        // 创建Map集合用于记录t字符串中的字母及其个数
        Map<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            char c = t.charAt(i);
            map.put(c, map.getOrDefault(c, 0) + 1);
        }

        // 初始化最小长度为一个较大的值
        int minLen = Integer.MAX_VALUE;
        // 初始化左右指针
        int left = 0, right = 0;
        // 用于记录当前窗口的长度
        int length = 0;
        // 用于记录最终结果的子串
        String result = "";

        // 遍历s字符串
        for (left = 0; left < s.length(); left++) {
            // 每次左指针移动时都重新创建一个countMap,复制map
            Map<Character, Integer> countMap = new HashMap<>(map);

            // 从当前left位置开始移动右指针
            for (right = left; right < s.length(); right++) {
                char c = s.charAt(right);
                // 如果右指针位置的字符在countMap中,减少它的计数
                if (countMap.containsKey(c)) {
                    countMap.put(c, countMap.get(c) - 1);
                    // 如果计数减少到0,移除该字符
                    if (countMap.get(c) == 0) {
                        countMap.remove(c);
                    }
                }

                // 当countMap为空时,说明当前窗口包含了t中的所有字符
                if (countMap.size() == 0) {
                    // 计算当前窗口的长度
                    length = right - left + 1;
                    // 更新最小长度和结果字符串
                    if (length < minLen) {
                        minLen = length;
                        result = s.substring(left, right + 1);
                    }
                    break;
                }
            }
        }

        return result;
    }
}

2.2 双指针-哈希集合

import java.util.HashMap;
import java.util.Map;

class Solution {
    // 创建Map集合用于记录t字符串中的字母及其个数
    Map<Character, Integer> tMap = new HashMap<>();
    // 创建Map集合用于记录s字符串滑动窗口过程中的字母及其个数
    Map<Character, Integer> sMap = new HashMap<>();

    /**
     * 找到字符串s中包含字符串t所有字符的最小子串
     *
     * @param s 字符串s
     * @param t 字符串t
     * @return 包含字符串t所有字符的最小子串
     */
    public String minWindow(String s, String t) {
        // 初始化tMap,用于记录t字符串中的每个字符及其出现次数
        for (int i = 0; i < t.length(); i++) {
            Character c = t.charAt(i);
            tMap.put(c, tMap.getOrDefault(c, 0) + 1);
        }

        // 初始化最小长度和结果字符串
        int minLen = Integer.MAX_VALUE;
        String result = "";

        // 滑动窗口的左右指针
        int left, right;
        // 遍历s字符串
        for (left = 0, right = 0; right < s.length(); right++) {
            // 获取当前字符
            Character c = s.charAt(right);
            // 将当前字符加入sMap中,记录其出现次数
            sMap.put(c, sMap.getOrDefault(c, 0) + 1);

            // 当滑动窗口中包含t字符串的所有字符时,收缩左边界
            while (check()) {
                // 更新最小长度和结果字符串
                int length = right - left + 1;
                if (length < minLen) {
                    minLen = length;
                    result = s.substring(left, right + 1);
                }
                // 移除左边界的字符,并更新sMap中的计数
                c = s.charAt(left);
                sMap.put(c, sMap.get(c) - 1);
                // 收缩左边界
                left++;
            }
        }

        // 返回最小窗口的子串
        return result;
    }

    /**
     * 检查当前滑动窗口是否包含t字符串中的所有字符
     *
     * @return 如果包含所有字符,返回true;否则返回false
     */
    public boolean check() {
        // 遍历tMap中的每个字符及其计数
        for (Map.Entry<Character, Integer> entry : tMap.entrySet()) {
            Character c = entry.getKey();
            // 如果sMap中当前字符的计数小于tMap中的计数,返回false
            if (sMap.getOrDefault(c, 0) < entry.getValue()) {
                return false;
            }
        }
        // 如果sMap中包含tMap中的所有字符及其计数,返回true
        return true;
    }
}

2.3 双指针-哈希数组

  • 如果能用哈希集合,那么大概率也可以使用哈希数组
class Solution {
    // 主方法,寻找字符串 s 中包含字符串 t 的最小窗口子串
    public String minWindow(String s, String t) {
        // 用于存储 t 中各字符的出现次数
        int[] tArr = new int[128];
        // 用于存储 s 中当前窗口各字符的出现次数
        int[] sArr = new int[128];

        // 初始化 tArr 数组
        for (int i = 0; i < t.length(); i++) {
            tArr[t.charAt(i)]++;
        }

        int left = 0, right = 0; // 左右指针初始化为 0
        int minLen = Integer.MAX_VALUE; // 最小长度初始化为无穷大
        String result = ""; // 最终结果字符串初始化为空

        // 移动右指针,扩展窗口
        while (right < s.length()) {
            sArr[s.charAt(right)]++; // 记录右指针指向字符的出现次数
            // 当窗口内的字符已经包含 t 中所有字符时,移动左指针缩小窗口
            while (check(sArr, tArr)) {
                int length = right - left + 1; // 计算当前窗口长度
                if (length < minLen) { // 如果当前窗口长度小于最小长度,更新最小长度和结果字符串
                    minLen = length;
                    result = s.substring(left, right + 1);
                }
                sArr[s.charAt(left)]--; // 左指针右移前,减少左指针指向字符的出现次数
                left++; // 左指针右移,缩小窗口
            }
            right++; // 右指针右移,扩大窗口
        }
        return result; // 返回结果字符串
    }

    // 检查当前窗口是否包含 t 中所有字符
    public boolean check(int[] sArr, int[] tArr) {
        // 检查大写字母 A-Z
        for (int i = 'A'; i <= 'Z'; i++) {
            if (sArr[i] < tArr[i]) {
                return false;
            }
        }
        // 检查小写字母 a-z
        for (int i = 'a'; i <= 'z'; i++) {
            if (sArr[i] < tArr[i]) {
                return false;
            }
        }
        return true; // 当前窗口包含 t 中所有字符
    }
}

2.4 优化

  • 上面两种方法,每次都需要调用check()函数,我们能不能优化一下呢?
class Solution {
    // 主方法,寻找字符串 s 中包含字符串 t 的最小窗口子串
    public String minWindow(String s, String t) {
        // 用于存储 t 中各字符的出现次数
        int[] tArr = new int[128];
        // 用于存储 s 中当前窗口各字符的出现次数
        int[] sArr = new int[128];

        // 初始化 tArr 数组,记录 t 中每个字符的出现次数,并统计不同字符的种类数
        int cnt = 0;
        for (int i = 0; i < t.length(); i++) {
            if (tArr[t.charAt(i)]++ == 0) {
                cnt++;
            }
        }

        int left = 0, right = 0; // 左右指针初始化为 0
        int minLen = Integer.MAX_VALUE; // 最小长度初始化为无穷大
        String result = ""; // 最终结果字符串初始化为空

        // 移动右指针,扩展窗口
        while (right < s.length()) {
            char c = s.charAt(right);
            sArr[c]++;
            // 当窗口中的某字符数量达到 t 中该字符的数量时,计数器 cnt 减 1
            if (sArr[c] == tArr[c]) {
                cnt--;
            }
            
            // 当计数器 cnt 为 0 时,窗口包含了 t 中所有字符
            while (cnt == 0) {
                int length = right - left + 1;
                // 更新最小长度和结果子字符串
                if (length < minLen) {
                    minLen = length;
                    result = s.substring(left, right + 1);
                }
                // 移动左指针,缩小窗口
                char x = s.charAt(left);
                sArr[x]--;
                // 当某字符数量小于 t 中该字符数量时,计数器 cnt 加 1
                if (sArr[x] < tArr[x]) {
                    cnt++;
                }
                left++;
            }
            right++;
        }
        return result; // 返回结果字符串
    }
}
  • 27
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神马都会亿点点的毛毛张

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值