【滑动窗口&哈希表】力扣Hot100:76. 最小覆盖子串题目解析(Java详解)

力扣Hot100题目解析:76. 最小覆盖子串(Java详解)

一、题目描述

给你两个字符串 s 和 t ,要求你在 s 中找出包含 t 所有字符的最小子串(要求所有字符都要包含,且顺序不限,字符可以重复)。如果不存在这样的子串,返回空字符串。

示例:

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

二、解题思路(滑动窗口+哈希表)

本题属于典型的滑动窗口问题,结合哈希表统计字符出现次数。

步骤解析:

  1. 用两个哈希表:
    • need 统计 t 中每个字符需要的数量。
    • window 统计当前窗口中每个字符的数量。
  2. 用两个指针 leftright 表示当前滑动窗口的左右边界,初始都为 0。
  3. 扩大右边界(right),把字符加入 window,更新窗口计数。
  4. 当窗口内已包含 t 所需的全部字符(用一个变量 valid 记录满足条件的字符种类数):
    • 缩小左边界(left),尽量让窗口变小,直到不再满足条件。
    • 过程中不断更新最小覆盖子串的位置和长度。
  5. 最后返回记录的最小子串即可。

这种方法时间复杂度为 O(n),空间复杂度为 O(字符种类数)。

三、Java代码实现(详细注释)

import java.util.HashMap;

public class Solution {
    public String minWindow(String s, String t) {
        // 统计t中的字符需求
        HashMap<Character, Integer> need = new HashMap<>();
        HashMap<Character, Integer> window = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        int left = 0, right = 0; // 滑动窗口
        int valid = 0; // 当前窗口满足需求的字符种类数
        // 记录最小覆盖子串的起始索引和长度
        int start = 0, len = Integer.MAX_VALUE;

        while (right < s.length()) {
            char c = s.charAt(right);
            right++;
            // 当前字符在t中需要
            if (need.containsKey(c)) {
                window.put(c, window.getOrDefault(c, 0) + 1);
                // 如果窗口中该字符数量和need中的一样,说明满足了这个字符
                if (window.get(c).intValue() == need.get(c).intValue()) {
                    valid++;
                }
            }
            // 当所有需求字符都满足时,尝试收缩左边界
            while (valid == need.size()) {
                // 更新最小长度及起始位置
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                char d = s.charAt(left);
                left++;
                if (need.containsKey(d)) {
                    if (window.get(d).intValue() == need.get(d).intValue()) {
                        valid--;
                    }
                    window.put(d, window.get(d) - 1);
                }
            }
        }
        // 没有找到则返回空字符串
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

代码详解:

  • need 记录t中每个字符需要出现的次数。
  • window 记录当前窗口内每个字符的出现次数。
  • valid 统计满足需求的字符种类数。
  • 每次右移窗口,更新window;当满足全部需求时,尝试收缩左边界,更新最小子串。
  • 返回记录的最小子串。

四、总结

本题通过滑动窗口+哈希表的经典组合,高效解决了最小覆盖子串问题。掌握这种方法能帮助你应对各种子串匹配和窗口类问题。

### Java 实现最小覆盖子串算法 对于最小覆盖子串问题,在字符串 `s` 中找到能覆盖字符串 `t` 所有字符的最小子串是一项挑战。下面展示了一个基于滑动窗口技术来解决问题的方法[^1]。 ```java import java.util.*; public class MinWindowSubstring { public String minWindow(String s, String t) { if (s == null || t == null || s.isEmpty() || t.isEmpty()) return &quot;&quot;; Map&lt;Character, Integer&gt; targetCount = new HashMap&lt;&gt;(); for (char c : t.toCharArray()) { targetCount.put(c, targetCount.getOrDefault(c, 0) + 1); } int requiredChars = targetCount.size(); int formed = 0; Map&lt;Character, Integer&gt; windowCounts = new HashMap&lt;&gt;(); int left = 0, right = 0; int[] ans = {-1, 0, 0}; while (right &lt; s.length()) { char character = s.charAt(right); windowCounts.put(character, windowCounts.getOrDefault(character, 0) + 1); if (targetCount.containsKey(character) &amp;&amp; windowCounts.get(character).intValue() == targetCount.get(character).intValue()) { formed++; } while (left &lt;= right &amp;&amp; formed == requiredChars) { character = s.charAt(left); if (ans[0] == -1 || right - left + 1 &lt; ans[0]) { ans[0] = right - left + 1; ans[1] = left; ans[2] = right; } windowCounts.put(character, windowCounts.get(character) - 1); if (targetCount.containsKey(character) &amp;&amp; windowCounts.get(character).intValue() &lt; targetCount.get(character).intValue()) { formed--; } left++; } right++; } return ans[0] == -1 ? &quot;&quot; : s.substring(ans[1], ans[2] + 1); } } ``` 上述代码实现了滑动窗口方法,通过两个指针(`left`, `right`)维护当前考察的窗口范围,并利用哈希表记录目标字符串`t`中的字符频率以及当前窗口内的字符频率。当窗口内包含了足够的`t`中字符时尝试收缩左边界以优化解的空间大小[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值