最短包含字符串的长度
题目描述
给定字符串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(1≤lengthstr1≤105),第二行也是一个字符串,代表 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(1≤lengthstr2≤105)。
输出描述:
输出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;
}