暴力解法
思路很简单,依次计算从各个字符开始的无重复子串的长度,时间复杂度O(n^2)
/**
* 暴力求解
* 261ms 7%
* 40M 5%
* @param s
* @return
*/
public int lengthOfLongestSubstring(String s){
int ans=0;
for(int i=0;i<s.length();i++){
String sub="";
for(int j=i;j<s.length();j++){
if(sub.contains(String.valueOf(s.charAt(j)))){
break;
}else{
sub= s.substring(i, j+1);
ans=Math.max(ans, sub.length());
}
}
}
return ans;
}
暴力解法优化
采用HashMap进行 判断是否重复步骤 优化
/**
* 引用HashMap 进行contains比较 O(n^2)
* 140ms 11%
* 40M 5%
* @param s
* @return
*/
public int lengthOfLongestSubstring2(String s){
int ans=0;
char[] chars = s.toCharArray();
int len=chars.length;
for(int i=0;i<len;i++){
Map<Character,Integer> map=new HashMap<>();
for(int j=i;j<len;j++){
if(map.containsKey(chars[j])){
break;
}else{
map.put(chars[j],1);
ans=Math.max(ans, map.size());
}
}
}
return ans;
}
滑动窗口 降低时间复杂度
以上两个解法的时间复杂度显然是 O(n^2) 参考大佬的思路,所以采用滑动窗口(左右指针)来降低复杂度 变为 O(n)
思路
-
总体而言,遍历过程 保持区间[start,end]区间内字符不重复
-
定义一个map存储<k,v> ,其中k为字符,v为字符位置+1,加1表示从字符位置的下一个才开始不重复。
-
定义不重复子串的开始位置为start ,结束位置为end。
-
随着end不断向后遍历,会遇到与[start,end]区间内字符相同的情况,此时将字符作为key,获取其value值,并更新start,此时[start,end]区间内依旧不存在重复字符。
-
无论是否更新start,都会更新其map数据结构和结果ans。
-
时间复杂度O(n)
代码
/**
* 使用滑动窗口 O(n)
* 6ms 85%
* 40M 5%
* @param s
* @return
*/
public int lengthOfLongestSubstring3(String s) {
int ans=0;
int len=s.length();
Map<Character,Integer> map=new HashMap<>();
for(int start=0,end=0;end<len;end++){
char c=s.charAt(end);
if(map.containsKey(c)){
start=Math.max(map.get(c), start);
}
ans=Math.max(ans, end-start+1);
map.put(s.charAt(end), end+1);
}
return ans;
}
纠错及补充
- 做题时,将map和list混淆,竟将map误认为key可以重复,就没想到通过key刷新value的值。