项目介绍
- 本项目通过分解各大厂的常见笔面试题,追本溯源至数据结构和算法的底层实现原理,知其然知其所以然;
- 建立知识结构体系,方便查找,欢迎更多志同道合的朋友加入项目AlgorithmPractice(欢迎提issue和pull request)。
什么是滑动窗口
- 在尽可能一次遍历中,在源串中找出目标串的匹配模式。
五道题目七种解法
- 序列匹配
- 最小覆盖子串
- 字符串子串包含检测
- 找所有字母异位词
- 最长无重复子串
正文开始
1、序列匹配
- 给定一个源字符串,再给一个目标字符串,看看源串中是否存在目标字符串的序列,比如:
- 正案例
- 源字符串:“1a2b3c4d”,
- 目标字符串:“1234”
- 很明显,源串中存在目标字符串的序列。
- 反案例:
- 源字符串:“1a2b3cd”,
- 目标字符串:“1234”
- 很明显,源串中不存在目标字符串的序列。
- 解法:因为我们的目的很简单,你只需要告诉我,存不存在,true or false,因此通过一次循环,source指针指向源串的位置,target指针指向目标串的位置,双指针同步工作,最后判断target指针是否等于目标串的长度即可,当然,这道题如果改成两者最长公共部分的序列,需要使用LCS的解法来处理,本题完整源码:SequenceExist。除了这种方法外,还有一个叫做字符串预处理的方法:字符串预处理法解决字符串匹配问题
- 主要代码:
while (sourcelength < source.length() && targetlength < target.length()){
if(targetchar[targetlength] == sourcechar[sourcelength]){
targetlength++;
}
sourcelength++;
}
return targetlength == target.length();
2、最小覆盖子串
- 给定一个源字符串,再给一个目标字符串,看看源串中是否存在目标字符串的序列,特别的是:对于这个序列,我们允许它在源字符串中乱序存在。但是如果仅仅是存在性判断,那么一个list或者hashmap完全可以搞定,将目标字符串存入list中,遍历源字符串,从list中删除这个字符,什么时候list空了,return true,否则reture false。
- 我们要求更进一步,假设存在多个目标字符串的字符存在于源字符串中,那么找出包含目标字符串中所有字符的最小区间,比如:
- 正案例
- 源字符串:“2a41b156d3c24d”,
- 目标字符串:“1234”
- 很明显,源串中乱序存在目标字符串的序列,但是这其中最小的覆盖子串的区间是“156d3c24”。
- 反案例:
- 源字符串:“1a2b3cd”,
- 目标字符串:“1234”
- 很明显,源串中根本不存在目标字符串的序列。
- 解法:这道题需要从滑动窗口的角度来考虑,完整源码:MinimumWindowSubstring。
- 主要代码:
while (right < source.length()) {
char cright = source.charAt(right);
right++;
if (targetMap.containsKey(cright)) {
int num = windowsMap.containsKey(cright) ? windowsMap.get(cright) + 1 : 1;
windowsMap.put(cright, num);
if (windowsMap.get(cright) == targetMap.get(cright)) {
count++;
}
}
while (count == targetMap.size()) {
if (right - left < subLength) {
begin = left;
subLength = right - left;
}
char cleft = source.charAt(left);
left++;
if (targetMap.containsKey(cleft)) {
if (windowsMap.get(cleft) == targetMap.get(cleft)) {
count--;
}
windowsMap.put(cleft, windowsMap.get(cleft) - 1);
}
}
}
3、字符串子串包含检测
- 描述:对于给定一个源字符串和目标字符串,看看源串中是否存在目标字符串,与刚才的最小覆盖子串不同,这次是字符串,而不是序列了。比如:
- 正案例
- 源字符串:“cd63412asd”,
- 目标字符串:“1234”
- 很明显,源串中乱序存在目标字符串。
- 反案例:
- 源字符串:“123c4d”,
- 目标字符串:“1234”
- 很明显,源串中不存在目标字符连续的串。
- 解法:这道题也是需要从滑动窗口的角度来考虑,无非是判断窗口与目标串是否一致,完整源码:PermutationinString。
- 主要代码:
if (right - left == target.length()) {
if (count == targetMap.size()) {
return true;
}
char cleft = source.charAt(left);
if(windowsMap.containsKey(cleft)){
windowsMap.put(cleft, windowsMap.get(cleft) - 1);
count--;
}
left++;
}
4、找所有字母异位词
- 描述:对于给定一个源字符串和目标字符串,如果源串中存在多个目标字符串,那么返回他们的其实地址。比如:
- 正案例
- 源字符串:“cd63412as2314d”,
- 目标字符串:“1234”
- 很明显,源串中乱序存在目标字符串,其实位置分别是[3,9]。
- 反案例:
- 源字符串:“123c4d”,
- 目标字符串:“1234”
- 很明显,源串中不存在目标字符连续的串。
- 解法:这道题也是需要从滑动窗口的角度来考虑,与字符串子串包含检测一样,无非是多个list来存储每一次符合的字符串起始地址,完整源码:FindallAnagrams。
- 主要代码:
if (right - left + 1 == target.length()) {
if (point == targetMap.size()) {
count.add(left);
}
char cleft = source.charAt(left);
if(windowsMap.containsKey(cleft)){
windowsMap.put(cleft, windowsMap.get(cleft) - 1);
point--;
}
left++;
}
5、最长无重复子串长度
- 描述:给定一个字符串,找出最长的不重复的字串,比如:
- 正案例
- 源字符串:“aassddffgghh”,
- 最长无重复子串长度:2
- 很明显,任意的as,或者sd、df、fg、gh,长度都是2。
- 解法:这道题有两种解法,桶方法,或者从滑动窗口的角度来考虑,桶方法和滑动窗口法的完整源码:LNRSubstring。
- 桶方法主要代码:
for (int begin = 0, j = 0; j < s.length(); j++) {
begin = Math.max(bottle[s.charAt(j)], begin);
count = Math.max(count, j - begin + 1);
bottle[s.charAt(j)] = j + 1;
}
while(right < s.length()){
Character c = s.charAt(right);
if(!windows.containsKey(c)){
windows.put(c,right);
count = windows.size() > count ? windows.size() : count;
}else {
while (left < right && windows.containsKey(c)){
windows.remove(s.charAt(left));
left++;
}
windows.put(c,right);
}
right++;
}