最短包含字符串的长度

最短包含字符串的长度

题目描述

给定字符串str1和str2,求str1的字串中含有str2所有字符的最小字符串长度。

输入描述:

输入包括两行,第一行一个字符串,代表 s t r 1 ( 1 ≤ l e n g t h s t r 1 ≤ 1 0 5 ) str1( 1 \leq length_{str1} \leq 10^5) str1(1lengthstr1105),第二行也是一个字符串,代表 s t r 2 ( 1 ≤ l e n g t h s t r 2 ≤ 1 0 5 ) str2( 1 \leq length_{str2} \leq 10^5) str2(1lengthstr2105)

输出描述:

输出str1的字串中含有str2所有字符的最小字符串长度,如果不存在请输出0。

示例1
输入
abcde
ac
输出
3
说明
“abc”中包含“ac”,且“abc”是所有满足条件中最小的。
示例2
输入
12345
344
输出
0

题解:

双指针还款法。设 l, r 表示包含 str2 的子串左右边界,初始时都为0,match 表示 str1 还欠 str2 多少个字符,初始值为 str2 的长度。

首先,我们需要使用哈希表 hash 记录 str2 中的不同字符出现次数,方便后面计数需要。

通过 r 从左往右遍历 str1 ,设当前位置 r = i,分以下几种情况讨论:

  • 首先在 hash 中将 str1[i] 字符的次数减一,如果减完之后,hash[str1[i]] >= 0 ,说明 str1 归还了 str2 一个字符,match减一;如果减完之后,hash[str1[i]] < 0,说明这个字符是目前 str2 不需要的,所以 macth 不变;

  • 若 match == 0,说明 str1 把字符都归还完了,此时的 str1[l…r] 是一个包含 str2 所有字符的子串,但不一定是最短的,因为有可能有些字符归还的有些多余,比如:str1=“adabbca”, str2=“acb”,在 r=5 的时候,match 为 0 ,但是区间 [0…5] 中 ‘a’ 归还了两次,归还 str1[0] 是多余的,即使把 str1[0] 拿回来,也不会亏欠 str2。所以此时我们需要向右移动 l ,初始时 l=0 ,把 str1[0] 拿回来之后,hash[str1[0]]++ ,如果 hash[str1[0]] 位置没 +1 之前是负数,说明即使拿回了也不会亏欠 str2 ,不停的移动 l ,直到出现了 hash[str1[l]] 为 0 时停止,因为此时如果拿回又要亏欠 str2 ,于是出现了一个从 l 出发的最小子区间,长度为 r-l+1 ,更新结果。但这个结果并不能保证是全局最小的,所以还需要往右扩展。但是后面的其余子串肯定比之前的 l 要靠右,因为之前 l 位置对应的最短子串已经找到。令 l++ ,并且 hash[str1[l]]++ ,此时亏欠了 str2 ,所以 match++。

  • r 继续向右扩展,直到 str1 遍历结束。

代码:
#include <cstdio>
#include <cstring>
#include <unordered_map>

using namespace std;

const int N = 100001;
const int INF = 1 << 30;

char s1[N], s2[N];

int main(void) {
    scanf("%s", s1);
    scanf("%s", s2);
    int len1 = strlen( s1 );
    int len2 = strlen( s2 );
    if ( len2 > len1 ) return 0 * puts("0");
    unordered_map<char, int> hash;
    for ( int i = 0; s2[i]; ++i )
        ++hash[s2[i]];
    int l = 0, r = 0, match = len2;
    int ret = INF;
    while ( s1[r] ) {
        --hash[s1[r]];
        if ( hash[s1[r]] >= 0 ) --match;
        if ( !match ) {
            while ( hash[s1[l]] < 0 )
                ++hash[s1[l++]];
            ret = min( ret, r - l + 1 );
            ++match;
            ++hash[s1[l++]];
        }
        ++r;
    }
    if ( ret == INF ) ret = 0;
    printf("%d\n", ret);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值