3_Longest Substring Without Repeating Characters
一、题目详情
题意很清晰,给定字符串,求出不含重复字母的最长子串的长度。
二、解题方法
第一种方法:直接暴力搜索
这种方法,求出给定字符串的每一个子串,判断子串是否满足没有重复字母的条件,由此求出最长的复合条件的子串。其中求字符串的所有子串,设定两个变量i
和j
,i是子串的开始位置,j是子串的结束位置。通过两层循环i=0:n
以及j=i+1:n
,使用substring(i,j)
方法求出所有子串。详细代码如下:
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0;
for(int i=0;i<s.length();i++)
for(int j=i+1;j<=s.length();j++)
if(allUnique(s.substring(i,j)))
res = Math.max(res,j-i);
return res;
}
public boolean allUnique(String line){
Set<Character> set = new HashSet<>();
char[] chs = line.toCharArray();
for(int i=0;i<chs.length;i++)
if(set.contains(chs[i])) return false;
else set.add(chs[i]);
return true;
}
}
可以直观的看出allUnique()
方法中还有一层循环,所以这种算法的时间复杂度为O(n^3)
.
第二种方法:滑动窗口
第一种方法十分naive,在leetcode上是不可能通过的,绝对TLE。但是我们可以重新来看看上一种方法,它比较了每一个字符串。假设i,j循环进行到i=i',j=j'的时候,我们得到substring(i',j')子串不满足条件时,i=i',j>j'的所有子串都不满足条件可以直接跳过。 所以我们将代码进行改进:class Solution {
public int lengthOfLongestSubstring(String s) {
int res=0,i=0,j=0;
char[] chs = s.toCharArray();
int n = chs.length;
if(n==0) return 0;
Set<Character> set = new HashSet<>();
while(i<n&&j<n){
if(!set.contains(chs[j])){
set.add(chs[j]);
res = Math.max(res,j-i+1);
j++;
}else{
set.remove(chs[i]);
i++;
}
}
return res;
}
}
为了方便我们使用了hashSet来对子串的字母进行存储。如果set包含chs[j],i就直接+1,并且(i+1)--j中符合条件的子串肯定没有之前已经记录下来的最大长度还长,所以j可以接着继续++,不需要将j置为i+1.
最差的情况下字符串的每一个字母都会被i和j分别遍历一次,所以时间复杂度为O(2n)=O(n)
.
第三种方法:进一步改进
前一种算法为O(2n),可以通过测试了,但是我们还可以进一步思考一下。如果当我们遍历到chs[j]时(chs是给定字符串的字符数组),如果不满足条件,并且chs[j] = chs[j'](j'输入[i,j)),这是我们可以直接将i设置为j'+1,因为i在j'之前和j构成的子串必定会因为chs[j']==chs[j]而不满足条件,我们用map来存储字母和下标的对应关系。详细代码如下:class Solution {
public int lengthOfLongestSubstring(String s) {
int res=0,i=0,j=0;
char[] chs = s.toCharArray();
int n = chs.length;
if(n==0) return 0;
Map<Character,Integer> map = new HashMap<>();
while(i<n&&j<n){
if(!map.containsKey(chs[j])){
map.put(chs[j],j);
j++;
res = Math.max(res,j-i);
}else{
int ind = map.get(chs[j]);
map.put(chs[j],j);
i = Math.max(i,ind+1);//*
j++;
res = Math.max(res,j-i);
}
}
return res;
}
}
注意*行,要确保i至少得和正常遍历时的i一样大(即i = Math.max(i,ind+1)),不能比起小,因为正常遍历的i之前的字母已经被前面的步骤计算过了。