leetcode
leetcode 2901 ~ 2950
最小覆盖子串

最小覆盖子串

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 56 ms, 内存: 16.3 MB


/*
 * 思路:
 * 使用滑动窗口方法,并结合Java Stream API来进行一些操作。
 * 使用Stream API来初始化目标字符频率表,
 * 其他逻辑与普通Java实现类似,但不强求使用Stream API处理整个问题。
 */

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

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

        Map<Character, Integer> targetFreq = t.chars().boxed()
            .collect(Collectors.toMap(k -> (char) k.intValue(), v -> 1, Integer::sum));

        int left = 0, right = 0;
        int required = targetFreq.size();
        int formed = 0;
        Map<Character, Integer> windowCounts = new HashMap<>();
        int[] ans = {-1, 0, 0};

        while (right < s.length()) {
            char c = s.charAt(right);
            windowCounts.put(c, windowCounts.getOrDefault(c, 0) + 1);

            if (targetFreq.containsKey(c) && windowCounts.get(c).intValue() == targetFreq.get(c).intValue()) {
                formed++;
            }

            while (left <= right && formed == required) {
                c = s.charAt(left);
                if (ans[0] == -1 || right - left + 1 < ans[0]) {
                    ans[0] = right - left + 1;
                    ans[1] = left;
                    ans[2] = right;
                }

                windowCounts.put(c, windowCounts.get(c) - 1);
                if (targetFreq.containsKey(c) && windowCounts.get(c).intValue() < targetFreq.get(c).intValue()) {
                    formed--;
                }
                left++;
            }
            right++;
        }

        return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
    }
}

解释

方法:

这道题使用了滑动窗口和双指针的方法。首先,使用一个字典记录字符串t中每个字符的数量。然后,使用两个指针i和j来表示当前的滑动窗口,初始时都指向s的开头。随着j的向右移动,如果当前字符在t中,就减少字典中对应字符的数量,并且减少计数器cnt的值。当cnt变为0时,说明当前窗口已经包含了t中的所有字符。接着,尝试移动左指针i,直到窗口不再包含t中的所有字符。在这个过程中,记录最小的窗口大小,并更新结果。最后,返回最小覆盖子串。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
在字典中记录字符串t中每个字符的数量的目的是什么?这与直接遍历s中的字符有何不同?
在字典中记录字符串t中每个字符的数量主要是为了快速判断滑动窗口内的子串是否包含t中的所有字符及其数量。这种方法使得每次移动窗口时,可以直接通过字典查看和更新字符的计数,而不需要每次都重新计算窗口中的字符是否满足条件。与直接遍历s中的字符相比,这种方法大大减少了重复计算,提高了算法的效率。
🦆
当cnt变为0时,你提到这意味着滑动窗口已包含t中的所有字符。请问这里的cnt是如何定义和操作的,为什么可以这样用来确定窗口状态?
这里的cnt是一个计数器,初始化为字符串t的长度,即t中字符的总数。每当滑动窗口包括一个t中的字符时,如果这个字符是t中需要的(即在字典中的计数大于0),则cnt减一。当cnt减到0时,意味着窗口已包含t的所有字符且每个字符的数量至少与t中的相等。因此,cnt为0是判断窗口已包含t中所有字符的有效方式。
🦆
在尝试移动左指针i以缩小窗口时,你提到要保证窗口仍包含t的所有字符。具体是如何判断窗口不再满足条件,以及如何调整字典中的字符计数的?
在移动左指针i以缩小窗口时,需要逐个将i指向的字符从窗口中排除,即将字典中对应字符的计数增加。每次字典中某个字符的计数从负数变为非负数时,如果这个字符是t中的字符,那么这意味着窗口不再包含t中足够的该字符,此时需要停止缩小窗口并移动右指针j继续寻找合适的窗口。这样可以确保窗口在尽可能小的前提下仍然满足包含t的所有字符。

相关问题