方法一:暴力法
最直观的解决方法是:遍历所有可能的子串,找出其中所有的无重复的子串,比较得出最长无重复子串的的长度。
//判断一个子串是否是无重复的
public boolean isUnique(char[] str, int start, int end) {
Set<Character> mySet = new HashSet<>();
for(int i=start; i<=end; ++i) {
if(!mySet.add(str[i])) {
return false;
}
}
return true;
}
public int lengthOfLongestSubstring(String s) {
char[] str = s.toCharArray();
int len = str.length;
int result = 0;
//遍历所有的子串
for (int i = 0; i < len; i++) {
for(int j = i + 1; j < len; ++j) {
//判断子串是否是无重复的
if(isUnique(str, i, j)) {
result = Math.max(result, j - i + 1);
}
}
}
return result;
}
方法二:优化
暴力方能解决问题,但是太慢,慢的原因是其中包含了太多的重复冗余计算,需要对其进行分析和优化。当子串[i, j]无重复,而子串[i, j+1]有重复,则右端指针后移就没有意义了,[i, j]就是以str[i]为开端的最长无重复子串。所以只需要找出以各个元素开头的最长无重复子串,对比他们的长度就可以求出最长无重复的子串。
public int lengthOfLongestSubstring(String s) {
char[] cs = s.toCharArray();
int result = 0;
Set<Character> mySet = new HashSet<>();
for(int i=0; i<cs.length; ++i) {
mySet.clear();
int len = 0, j = i;
while(j < cs.length && mySet.add(cs[j])) {
j += 1;
len += 1;
}
result = Math.max(result, len);
}
return result;
}
方法三:再优化
上个方法中,如果[i, j]为i为开端的最长无重复子串,那么如果str[j+1]包含在[i+1, j]中,则以str[i+1]为开端的最长无重复子串就是[i+1, j],它的长度肯定是小于[i, j]的,所以左端指针可以直接后移,除非str[j+1]没包含在[i+1, j]。
public int lengthOfLongestSubstring(String s) {
char[] cs = s.toCharArray();
Set<Character> mySet = new HashSet<>();
int slow = 0, fast = 0;
int result = 0;
while(fast < cs.length) {
if(mySet.add(cs[fast])) {
fast++;
} else {
mySet.remove(cs[slow++]);
}
result = Math.max(result, fast - slow);
}
return result;
}
方法四:最后优化
上边的方法中,左端指针通过判断子串右边的元素是否包含在子串里来决定是否右移,其实可以通过记录位置直接让子串指针跳到某个位置,达到子串不包含子串右边字符的目的。
public int lengthOfLongestSubstring(String s) {
char[] cs = s.toCharArray();
int result = 0, start = 0;
Map<Character, Integer> myMap = new HashMap<>();
for(int i=0; i<cs.length; ++i) {
if(!myMap.containsKey(cs[i]) || myMap.get(cs[i]) < start) {
result = Math.max(result, i - start + 1);
} else {
start = myMap.get(cs[i]) + 1;
}
myMap.put(cs[i], i);
}
return result;
}