上周末去滑雪了,人生第一次滑雪,第一把居然一次也没摔跤!
中午吃饭拍的喵星人~
题目
给定长度m的字符串aim,以及一个长度为n的字符串str,问能否在str中找到一个长度为m的连续子串,使得这个子串刚好由aim的m个字符组成,顺序可以随机,返回任意满足条件的其中一个子串的起始位置,未找到返回-1。
解题思路一
本题就是想在主串中找到一个子串,这个子串需要满足与目标串长度相同,且组成字符种类及每个字符出现次数均相同的!
首先比较容易想到的思路是滑动窗口。窗口大小为目标串aim大小,逐次移动,也就是一共需要移动str.length()-aim.length()次,每次我们需要比对当前串与目标串是否字符种类相同且各个字符出现次数相同这两个条件!那么如何实现呢?计数法!因为字符的元素都是a~z和A ~ Z之间的,那么我们可以借助数组来实现一个类似hashmap的计数器。这个数组大小256,我们首先创建一个count数组,大小256,首先统计aim子串中各字符出现的频次,以每个字符为数组下标,进行++运算来统计出现频次。然后,每次移动窗口,我们都将当前子串的元素为数组下标,将数组下标对应的频次进行–操作,当出现某个元素频次为负数时,那一定不匹配,窗口移动,继续下一次的匹配!如果当前串进行–后没有出现频次为负数情况,返回当前窗口开始下标,即为所求!
注:每次匹配需要对长度为m的子串进行频次统计++与–操作,并且一共需要统计主串长度-子串长度次,所以时间复杂度为O(n*m)。
代码实现
public static int containExactly1(String s,String a){//O(n*m)
if (s==null||a==null||s.length()<a.length()){
return -1;
}
char[] str=s.toCharArray();
char[] aim=a.toCharArray();
for(int L=0;L<=str.length-aim.length;L++){
if(isCountEqual(str,L,aim)){
return L;
}
}
return -1;
}
public static boolean isCountEqual(char[] str,int L,char[] aim){
int[] count=new int[256];
for(int i=0;i<aim.length;i++){
count[aim[i]]++;
}
for(int i=0;i<aim.length;i++){
if (count[str[L+i]]--==0]){
return false;
}
}
return true;
}
解题思路二
方法二对上面方法进一步进行优化,依旧是滑动窗口的思路,只是法一每次都需要对长度m的子串进行频次统计操作,那么能不能滑动窗口从头到尾平移中,只对每个字符统计一次就得出结果呢? 这相当于两层for循环变为一层了!
我们可以实现一个类似“借贷”的思路,我们通过count数组对aim子串进行频次统计后,建立滑动窗口。这次我们定义一个无效字符变量,含义就是当前目标串是我们拥有的,那么每个窗口中的字符如果与我们拥有的相匹配那么需要借出一个,相应的频次要减一,可能出现超借为负情况。在每次窗口移动时,伴随有新的元素进入,那么继续如果有相同的则继续做减法,原来元素出窗口了,那么就是元素归还要做加法!在窗口移动到某个位置时,当前无效字符变量为零,表示当前窗口所有元素全部借出,同时也没有超借的情况,那么窗口起始位置即为所求!
代码实现
public static int containExactly2(String s,String a){//O(n)
if(s==null||a==null||s.length()<a.length()){
return -1;
}
char[] aim=a.toCharArray();
int[] count=new int[256];
for (int i=0;i<aim.length;i++){
count[aim[i]]++;
}
int M=aim.length;
char[] str=s.toCharArray();
int inValidTimes=0;
int R=0;
for (;R<M;R++){
if (count[str[R]]--<=0){
inValidTimes++;
}
}
for(;R<str.length;R++){
if (inValidTimes==0){
return R-M;
}
if(count[str[R]]--<=0){
inValidTimes++;
}
if(count[str[R-M]]++<0){
inValidTimes--;
}
}
return inValidTimes==0?R-M:-1;
}
圣诞快到了,今年大意了,没有提前抢肯德基圣诞桶,最后挣扎两天,看看能不能抢到!
总结
算法的学习是个长期的过程,需要一直不断的去刷去总结来保持手感!
同许多在算法道路上不断前行的人一样,不断练习,修炼自己!
如有博客中存在的疑问或者建议,可以在下方留言一起交流,感谢各位!
觉得本博客有用的客官,可以给个点赞+收藏哦!
喜欢的朋友们可以关注下,以后除了算法,还会更新其他方面的学习文章!