题目
最长不重复子串
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
思路
做了LeetCode1,那题是找到唯一的a+b=target,其遍历到a,计算出b,再去发现是否contains,和这题很类似。所以直接Hash的思路。
暴力思路
以a[0],a[1]...开头,其结果各不一样,所以n*n
滑动窗口优化思路
我一直以来都知道复用的思想,看了官方答案,知道了这叫滑动窗口。
当a[0]确定,然后向后遍历,到某一点终止,得出a[0]下的最优解时,其实对于a[1],这个解也是成立的,同时由于少了第一个数,可以继续向后遍历试试
动态规划思路
假设前n个数已得出最优解,且最优解已包含最后一个数,那么新的后面的一个数,如果和之前有重复,那么需要从重复的那一位开始到当前,这是新的解集,1;反之,这个位上的最优解要多1个,+1。
暴力代码
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] chars = s.toCharArray();
int len = chars.length;
Set<Character> set = new HashSet<>();
int max = 0;
for(int i = 0; i < len; i ++) {
int total = 0;
for(int j = i; j < len; j ++) {
Character c = (Character)chars[j];
if(!set.contains(c)) {
total ++;
set.add(c);
} else {
break;
}
}
max = Math.max(max, total);
set.clear();
}
return max;
}
}
滑动窗口优化代码
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] chars = s.toCharArray();
int len = chars.length;
Set<Character> set = new HashSet<>();
int max = 0;
int endIndex = 0;
for(int i = 0; i < len; i ++) {
Character c = (Character)chars[i];
if(!set.contains(c)) {
max ++;
set.add(c);
} else {
endIndex = i;//从这里开始还没扫过
break;
}
}
if(endIndex == 0) {//如果第一遍能顺利遍历,那么就是整长啊
return max;
}
int lastTotal = max;
for(int i = 1; i < len; i ++) {
Character last = (Character)chars[i - 1];
set.remove(last);//滑动窗口第一个数不要了
lastTotal --;
for(int j = endIndex; j < len; j ++) {//从滑动窗口的末端开始找
Character c = (Character)chars[j];
if(!set.contains(c)) {
lastTotal ++;
set.add(c);
endIndex = j + 1;
} else {
endIndex = j;
break;
}
}
max = Math.max(max, lastTotal);
}
return max;
}
}
动态规划代码
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] chars = s.toCharArray();
int len = chars.length;
Map<Character, Integer> map = new HashMap<>();
int max = 0;
int total = 0;
int startIndex = 0;//上个解的startIndex
for(int i = 0; i < len; i ++) {
Character c = (Character)chars[i];
if(!map.containsKey(c)) {
total ++;
max = Math.max(max, total);
} else {
int index = map.get(c);
//包含这个数,前面的所有数都要被扔掉
for(int j = startIndex; j <= index; j ++) {
map.remove(chars[j]);
}
startIndex = index + 1;
//total等于[startIndex,i]
total = i - startIndex + 1;
}
map.put(c, i);
}
return max;
}
}
结果我发现我的各种方法都和官方有出入
我的暴力对比官方暴力
官方是枚举2个点,然后开始进行运算。我的是枚举起始点,开始运算。
我的滑动窗口对比官方滑动窗口
官方的思路是左边定死,右边移动,右边在移动的过程中,碰到重复的了,左边移动,再让右边移动看看还重复不重复了。知道产生了新的不重复,右边可以继续向下移动了。
我的思路和官方的思路一样的,就是我的代码冗余了很多。
官方滑动窗口的优化
i,j
j在向右走的时候,碰到了重复的,然后取得这个重复的index,下一次i直接跳到这个重复的index后即可。
我发现这个和我的动态规划思路是一样的
分析本质
本质是一个数组上的一片连续的区域。用滑动窗口来解决真的再合适不过。在右滑过程中,碰到重复的,需要找到重复的index,然后左边移到这个index的后面,继续右滑。
在这个过程中,记录最大值即可。
收获
get新名词,滑动窗口