leetcode-day12-最小覆盖子串

系列名 leetcode hot 100

题目名 最小覆盖子串

描述

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

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

解题思路

1、暴力求解,以t的长度作为窗口在s上滑动,判断窗口内的子串是否包含t中所有字符。接着扩大窗口长度知道最大窗口为s的长度。复杂度超过O(n^3)

2、双指针+hash优化,哈希表记录子串中字符频次和窗口中字符频次来确定左右指针是否能拓展和收缩

代码段

// Java
// 双指针+hash
public String minWindowx(String s, String t) {
        //维护s串中滑动窗口中各个字符出现次数
        Map<Character, Integer> smap = new HashMap<>();
        //维护t串中各个字符出现次数
        Map<Character, Integer> tmap = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            tmap.put(t.charAt(i), tmap.getOrDefault(t.charAt(i), 0)+1);
        }
        String res="";
        //cnt维护s串[left,right]中满足t串的元素的个数,记录相对应字符的总数 len对应找到的子串的长度
        int len=Integer.MAX_VALUE,count=0;
        //区间[left,right]表示当前滑动窗口
        for (int left=0,right = 0; right < s.length(); right++) {
            smap.put(s.charAt(right), smap.getOrDefault(s.charAt(right), 0)+1);
            //如果ht表中也包含当前字符
            if (tmap.containsKey(s.charAt(right))) {
                //并且hs表中的字符个数<=ht表中的字符个数,说明该字符是必须的,并且还未到达字符串t所要求的数量
                if (smap.get(s.charAt(right))<=tmap.get(s.charAt(right))) {
                    count++;
                }
            }
            //收缩滑动窗口
            //如果左边界的值不在ht表中 或者 它在hs表中的出现次数多于ht表中的出现次数
            while(left < right && (!tmap.containsKey(s.charAt(left)) || smap.get(s.charAt(left)) > tmap.get(s.charAt(left)))){
                smap.put(s.charAt(left),smap.get(s.charAt(left)) - 1);
                left++;
            }
            //此时滑动窗口包含符串 t 的全部字符
            if (count==t.length()&&right-left+1<len) {
                len=right-left+1;
                res=s.substring(left,right+1);
            }
        }
        return res;
    }


// 优化代码
class Solution {
    
    public String minWindow(String s, String t) {
    int[] map = new int[128];
    // 遍历字符串 t,初始化每个字母的次数
    for (int i = 0; i < t.length(); i++) {
        char char_i = t.charAt(i);
        map[char_i]++;
    }
    int left = 0; // 左指针
    int right = 0; // 右指针
    int ans_left = 0; // 保存最小窗口的左边界
    int ans_right = -1; // 保存最小窗口的右边界
    int ans_len = Integer.MAX_VALUE; // 当前最小窗口的长度
    int count = t.length();
    // 遍历字符串 s
    while (right < s.length()) {
        char char_right = s.charAt(right);

        // 当前的字母次数减一
        map[char_right]--;
       
        //代表当前符合了一个字母
        if (map[char_right] >= 0) {
            count--;
        }
        // 开始移动左指针,减小窗口
        while (count == 0) { // 如果当前窗口包含所有字母,就进入循环
            // 当前窗口大小
            int temp_len = right - left + 1;
            // 如果当前窗口更小,则更新相应变量
            if (temp_len < ans_len) {
                ans_left = left;
                ans_right = right;
                ans_len = temp_len;
            }
            // 得到左指针的字母
            char key = s.charAt(left);
            // 因为要把当前字母移除,所有相应次数要加 1
            map[key]++;
            //此时的 map[key] 大于 0 了,表示缺少当前字母了,count++
            if (map[key] > 0) {
                count++;
            }
            left++; // 左指针右移
        }
        // 右指针右移扩大窗口
        right++;
    }
    return s.substring(ans_left, ans_right + 1);
}

}

// Kotlin

// Python

// Go

// JavaScript

// C++

 // Rust
 

技巧总结

一般设计子串问题,优先考虑用双指针构造滑动窗口+hash查找法解决,一般字符串如果是全字母且不区分大小写的情形,注意考虑字母数组来统计词频辅助解决。

链接

题目

最小覆盖子串

题解

题解

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值