最小覆盖子串-Java版本

本文介绍如何使用快慢指针技巧求解字符串 's' 中包含字符串 't' 所有字符的最短子串。通过HashMap统计字符需求和窗口内出现次数,动态调整窗口大小来找到满足条件的子串。实例演示了如何在'EBBANCF'中找到包含'ABC'的最短子串'BANC'。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package com.areio.suanfa.zifuchuan;

/*最小覆盖子串
 * 给定两个字符串S和T,在S中找出包含T中全部字母的最短子串,如果S中没有这个子串,则返回空字符串,如果存在这样一个字符串,则可认为答案是唯一的
 *
 * S="EBBANCF",T="ABC"  返回BANC
 * 第一次窗口扩大到valid=need.size()是子串为EBBANC,此时left=0,right=6,窗口区间[0,6)
 * 而need={A=1,B=1,C=1},window={A=1,B=2,C=1} window中三个字符都已满足期待出现的次数,所以valid=3
 *
 *
 * */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/*
 * s是给定的字符串
 * t是目标字符串
 *
 * 快慢指针在数组上的应用
 * */

/*注意:
*1、在扩大和缩小窗口的时候要维护valid
*2、在缩小窗口的时候要更新最终结果
*
* */
public  class minWindow {
    public static String minWindow(String s,String t){
        //两个字符计数器,分布统计每个字符出现的次数,key是字符,val是出现次数
        //其中need代表目标字符串每个字符需要出现次数,window统计在当前窗口中出现目标字符串中每个字符的次数
        Map<Character,Integer> need=new HashMap<>();
        Map<Character,Integer> window=new HashMap<>();
        //统计目标字符串中每个字符需要出现的次数,比如t=“abc”,那么need={a=1,b=1,c=1}
        char[] tArray = t.toCharArray();
        for (char c: tArray) {
            //添加或者覆盖某键值对
            //need.getOrDefault(c,0)代表如果该key存在,则取key对应的值,不存在则得到默认值0
            need.put(c,need.getOrDefault(c,0)+1);
        }
        char[] sArray = s.toCharArray();
        //设置快慢指针的初始位置,此时窗口[left,right)=[0,0)为空
        int left=0,right=0;
        //valid代表满足要求的字符个数,比如need={a=1,b=2} 而window={a=1,b=1}那么只有a达到预期次数,此时valid=1
        int valid=0;
        //记录最小覆盖子串的起始索引及其长度
        int start=0,len=Integer.MAX_VALUE;

        while(right<s.length()){
            //将要加入到窗口的字符
            char c=sArray[right];
            //扩大窗口
            right++;
            //更新窗口内的数据
            if (need.containsKey(c)){
                //如果need需要这个key,就在统计窗口中出现该字符的次数
                window.put(c,window.getOrDefault(c,0)+1);
                //如果窗口中出现该字符的次数等于目标子串中需要这个字符的次数
                if(window.get(c)==need.get(c)){
                    valid++;
                }
            }
            //是否要开始缩小窗口;条件是一旦need中所有的字符都满足了期待值,那么就立马开始收缩,一旦窗口大小缩小到开始不满足need中所有的字符有一个不满足期待出现的次数,即窗口中所表示的字符串没有包含T中全部的字符时
            while (valid==need.size()){
                //长度相等之外,还需满足窗口中出现每个字符串的个数是否与目标字符串每个字符所需出现的次数一致
                 /* S="EBBANCF",T="ABC"  返回BANC
                  * 第一次窗口扩大到valid=need.size()是子串为EBBANC,此时left=0,right=6,窗口区间[0,6)
                  * 而need={A=1,B=1,C=1},window={A=1,B=2,C=1} window中三个字符都已满足期待出现的次数,所以valid=3
                  * */
                if (right-left<len){
                    //更新最小覆盖子串,一旦进入循环就代表当前这个子串是有覆盖目标子串,但是未必是最小的
                    start=left;
                    len=right-left;

                }
                //将要被踢出窗口的字符
                char d=sArray[left];
                //缩小窗口
                left++;
                //更新窗口内的数据
                if (need.containsKey(d)){

                    //如果need需要这个key,且恰好这个字符在窗口中出现的次数等于它需要出现的次数,那么缩小完 这个字符出现的次数-1,必然满足条件的字符就会-1
                    if (window.get(d)==need.get(d)){
                        valid--;
                    }
                    window.put(d,window.getOrDefault(d,0)-1);
                }
            }
            //重复扩大窗口和缩小窗口的过程
        }
        return len==Integer.MAX_VALUE?"":s.substring(start,start+len);
    }

    public static void main(String[] args) {
        String result=minWindow("EBBANCF","ABC");
        System.out.println(result);
    }

}

### Java最小覆盖子串的实现 最小覆盖子串问题是经典的滑动窗口问题之一,目标是在字符串 `s` 中找到包含另一个字符串 `t` 的所有字符的子串。以下是基于滑动窗口方法的一种高效解决方案。 #### 解决方案描述 该算法通过维护两个指针(左指针和右指针)来表示当前窗口范围,并利用哈希表记录所需字符及其数量以及当前窗口中的字符分布情况。当满足条件时,尝试收缩左侧边界以寻找更的有效子串[^1]。 下面是完整的 Java 实现: ```java import java.util.*; public class MinWindowSubstring { public static String minWindow(String s, String t) { if (s == null || t == null || s.isEmpty() || t.isEmpty()) return ""; Map<Character, Integer> need = new HashMap<>(); Map<Character, Integer> window = new HashMap<>(); // 初始化need map for (char c : t.toCharArray()) { need.put(c, need.getOrDefault(c, 0) + 1); } int left = 0; int right = 0; int valid = 0; // 记录最小覆盖子串的起始索引及长度 int start = 0; int len = Integer.MAX_VALUE; while (right < s.length()) { char c = s.charAt(right); right++; // 如果c是所需的字符,则更新window map if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) + 1); if (window.get(c).equals(need.get(c))) { 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).equals(need.get(d))) { valid--; } window.put(d, window.get(d) - 1); } } } return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len); } public static void main(String[] args) { System.out.println(minWindow("ADOBECODEBANC", "ABC")); // 输出:"BANC" } } ``` 上述代码实现了滑动窗口的核心逻辑,其中: - 使用了两个哈希表分别存储目标字符串 `t` 和当前窗口内的字符频率。 - 右侧指针不断扩展窗口直到满足条件;随后左侧指针逐步缩小窗口直至不再符合条件为止[^1]。 此方法的时间复杂度为 O(n),空间复杂度取决于输入字符串的不同字符数。 --- #### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值