「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战」。
给定长度为m的字符串aim,以及一个长度为n的字符串str, 问能否在str中找到一个长度为m的连续子串, 使得这个子串刚好由aim的m个字符组成,顺序无所谓, 返回任意满足条件的一个子串的起始位置,未找到返回-1
一、分析
str2:acbca (a:2,c:2,b:1),5个字符
str1:ccaab (a:2,c:2,b:1),5个字符
顺序无所谓,种类必须一样,每种字符的个数也得一样,叫做变形词
如果存在str1的子串是str2的变形词的话,返回开始位置
遍历str2,比如ccaba,得出一张表,两个变量,(a=2,b=1,c=2,all=5)
维持一个长度为5个窗口
str1:cccbabacbca......
str1定义一个欠账表,窗口再扩的时候,c进来**|c|,还款一个c,c减减,(a=2,b=1,c=1,all=4),窗口再扩一个位置,c进来|cc|,还款一个c,c减减,(a=2,b=1,c=0,all=3),窗口再扩一个位置,c进来|ccc|,还款一个c,c减减,(a=2,b=1,c=-1,all=3)** 说明不是一次有效的还款,all不动还是all=3,窗口再扩的时候,b进来**|cccb|,还款一个b,b减减,(a=2,b=0,c=-1,all=2),窗口再扩的时候,a进来|cccba|,还款一个a,a减减,(a=1,b=0,c=-1,all=1),此时窗口形成了,长度为5,|cccba|不是str2的变形词,all不等于0,有效的还款all !=0 ,说明一定有对不上的地方**,窗口再扩的时候,b进来,c出去c|ccbab|,还款一个b,b减减,出去一个c,c加加,(a=1,b=-1,c=0,all=1),依次类推......
count:欠债表
0 1 2 3 4 5 6,窗口长度为3
0~2,3位置上看是否有效
1~3,4位置上看是否有效
2~3,5位置上看是否有效
3~5,6位置上看是否有效
4~6,结束了,还需要看是否有效
二、实现
public static int containExactly3(String s1, String s2) {
if (s1 == null || s2 == null || s1.length() < s2.length()) {
return -1;
}
char[] str2 = s2.toCharArray();
int M = str2.length;
// 欠债表
int[] count = new int[256];
for (int i = 0; i < M; i++) {
count[str2[i]]++;
}
int all = M;
char[] str1 = s1.toCharArray();
int R = 0;
// 0~M-1
for (; R < M; R++) { // 最早的M个字符,让其窗口初步形成
if (count[str1[R]]-- > 0) {
all--;
}
}
// 窗口初步形成了,并没有判断有效无效,决定下一个位置一上来判断
// 接下来的过程,窗口右进一个,左吐一个
for (; R < str1.length; R++) {
if (all == 0) { // R-1
return R - M;
}
// 右进一个减减
if (count[str1[R]]-- > 0) {
all--;
}
// 左吐一个加加
if (count[str1[R - M]]++ >= 0) {
all++;
}
}
return all == 0 ? R - M : -1;
}
三、总结
字串一定是连续的,子序列不一定是连续的。
滑动窗口