题目
给定长度为m的字符串aim,以及一个长度为n的字符串str
问能否在str中找到一个长度为m的连续子串,使得这个子串刚好由aim的m个字符组成,顺序无所谓
返回任意满足条件的一个子串的起始位置,未找到返回-1
输入:
aabbccddffaa
ddcc
输出:
4
分析
使用滑动窗口加欠债表实现
1、构建一个欠债表,将aim中的字符串放入欠债表,并记录每个元素欠的个数
2、建立窗口,窗口大小和aim大小一致,将str前面aim.length个元素和欠债表进行比对,每出现一个欠债表的元素,那么欠债表中欠的元素
的个数减一,如果欠债表中不欠该元素,那么无效元素+1
3、第二步建立完窗口后,如果此时正好无效元素为0个,那就证明不欠债了(因为窗口中的元素和aim一样多,如果窗口没有无效元素,那么就都是有效元素,有效元素个数和aim一样多,那就是正好匹配上了被,此时直接返回结果就行)
如果有无效元素,那么就是还欠债,就可以开始滑动窗口了,
- 窗口每次向后滑动一个格子,判断新进来的元素是不是在欠债表中有,有的话欠债减一,没有的话,欠的个数也会减一,原本不欠的话,欠的个数是0,那么此时会变成负数,如果变成负数那么就是无效元素,无效元素加一
- 窗口大小不变的情况下向后滑动,那么之前窗口中最前面的一个元素必定会从窗口中出去,那么这时候欠债表中这个元素欠的个数就要加一,如果原本这个元素欠的就是负数,那么他原本就是无效元素,是无效元素的话,无效元素减一
4、在窗口每滑动一个格判断一次无效元素是不是等于0,如果等于0就返回当前窗口的起始位置就可以了
5、如果到最后也没找到,按题目要求返回-1
实现
public static int search(String a, String b){
char[] aArr = a.toCharArray();
char[] bArr = b.toCharArray();
//欠债表,ASCII码个数个数组个长度,用来记录每个元素出现次数
int[] count = new int[256];
for (int i = 0; i < bArr.length; i++){
count[bArr[i]]++;//将b中的中每一个元素放入欠债表
}
// 记录无效点数
Integer inValidTimes = 0;
int n = 0;// 滑动窗口的起始位置
for (;n < bArr.length; n++){// 构建滑动窗口
// 如果A的前四个中添加了b没有的元素,那么视为无效元素,无效元素个数加一
if(count[ aArr[n] ] -- == 0){
inValidTimes++;
}
}
// 从上面的循环出来后,此时n的位置为bArr.length,不需要再重新赋值了,直接跑
for (; n < aArr.length; n++){
if(inValidTimes == 0){//如果不欠债了,直接返回当前窗口的起始下标
return n - bArr.length + 1;
}
// 如果当前元素在欠债表中有,欠的债加一
if(count[ aArr[n] ] -- == 0){
inValidTimes++;
}
// 如果窗口前面弹出的元素在欠债表中多给了,欠债表中的元素减一
if(count[ aArr[n-bArr.length] ] ++ < 0){
inValidTimes--;
}
}
// 最后一个窗口没有判断是否匹配就从循环走出来了,所以这里需要再判断一下
return inValidTimes == 0 ? n - bArr.length + 1 : -1;
}