情境:给定一个字符串chars和一个输入字符串input,在input中寻找包含chars所有字符的最小窗口。例如,求input="ABBACBAA",chars="AAB"的最小窗口。
思路:如果这个算法使用暴力解法,使用内外循环遍历的话,时间复杂度过高,程序冗余。所以基于滑动窗口的方法,就可以想到散列表的思路,可以将目标字符串先映射到一个数组中,然后通过对输入字符串进行遍历,利用数组的查找时间复杂度是O(1)的特点可以大大降低时间复杂度。大致思路是,从头开始遍历,当窗口中包含目标字符串中所有字符时,开始尝试缩小窗口,并更新最小窗口。
算法步骤:
1.为了将字符串映射到数组中,可以考虑到的是ASCII的关系,可以将字符的ascii码作为数组的下标,首先为目标字符串定义一个数组shouldfind,数组大小为256,初始值都为0,先遍历一遍目标字符串,然后数组中对应字符的ascii所对应的位置加1。
2.同时需要定义另一个数组,用于遍历输入字符串,hasfound,大小为256,初始值为0,hasfound用于存储输入字符串遍历过程中,目标字符串所包含的字符。当查询到shouldfind所对应的数值不为0,则说明目标字符串中包含该字符,所以在hasfound中对应位置加1.
3.引入查询正确的字符个数cnt,cnt表示当前遍历输入字符串中包含目标字符串中字符的个数,注:计数过程中同一个字符的数量不能超过目标字符串所包含该字符的个数,因此应该建立hasfound和shouldfind的不等关系。
4.当cnt达到目标字符串的长度时,开始缩小窗口,引入一个下标变量j,从0开始遍历,逐渐缩小窗口,并更新最小窗口。直到遍历结束。
代码:
import java.util.*;
public class MinLengthWindow {
public static void minLengthWindow(String input,int iplen,String chars,int charlen)
{
int[] shouldfind=new int[256];
int[] hasfound=new int[256];
int j=0,cnt=0,start,finish,minwindow=Integer.MAX_VALUE;
for(int i=0;i<charlen;i++)
shouldfind[chars.charAt(i)]+=1;//先根据目标字符串,确定需要找的元素。
start=0;
finish=iplen;
for(int i=0;i<iplen;i++)
{
if(shouldfind[input.charAt(i)]==0)//如果是不是目标字符串中的字符则省略。
continue;
hasfound[input.charAt(i)]+=1;
if(shouldfind[input.charAt(i)]>=hasfound[input.charAt(i)])//防止多找。
cnt++;//说明又找到一个chars中的字符
if(cnt==charlen)//当chars全部找到之后,开始缩小窗口。
{
while(shouldfind[input.charAt(j)]==0||hasfound[input.charAt(j)]>shouldfind[input.charAt(j)])//逐渐缩小窗口
{
if(hasfound[input.charAt(j)]>shouldfind[input.charAt(j)])
hasfound[input.charAt(j)]--;
j++;
}
if(minwindow>(i-j+1))
{
minwindow=i-j+1;
start=j;
finish=i;
}
/**if(shouldfind[input.charAt(j)]!=0&&hasfound[input.charAt(j)]>shouldfind[input.charAt(j)])
{
hasfound[input.charAt(j)]--;
cnt--;
}*/
}
}
System.out.println("start:"+start+" and finish:"+finish+" "+input.substring(start, finish+1));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
String input=sc.nextLine();
String chars=sc.nextLine();
minLengthWindow(input,input.length(),chars,chars.length());
}
}
测试结果
请输入输入字符串:
ABBACBAA
请输入目标字符串:
AAB
start:5 and finish:7
输入字符串中最小窗口窗口包含的子串:BAA
详解:如上所示:input="ABBACBAA",chars="AAB".首先定义两个数组int shouldfind[256],int hasfound[256]。将chars写入shouldfind:遍历chars,shouldfind[chars.charAt(i)]+1,该处以字符的ascii作为该字符在数组中的下标,便于后面遍历输入字符串时查找。然后遍历input,先查找shouldfind[input.charAt(i)]是否为0,如果为0则略过,说明不在目标字符串中。若不为0,则hasfound[input.charAt(i)]+1。cnt作为查找到正确的符合要求的字符的个数,需要比较shouldfind[input.charAt(i)]与hasfound[input.charAt(i)]的值,只有当前者不小于后者时,才执行cnt++,说明该字符未找全,否则说明该字符找全了,不需要增加。当cnt等于目标字符串长度时,说明当前窗口已经包含所有目标字符,接着进行缩小窗口,从0开始逐个排查。若有更小窗口则更新窗口。直到遍历结束。