给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
1、一开始我以为只是算出来这个字符串只需要计算不重复的字符的总数,那太简单了,只要弄一个HashSet,给所有的字符全扔进去,最后计算一下长度就得出来了。
private static Integer getLength(String s){
if (s == null || s.length() == 0) {
return 0;
}
//把字符串拆开逐个放到一个map中,如果有key char[] charArray = s.toCharArray();
HashSet<Character> characters = new HashSet<>();
for (char c : charArray) {
characters.add(c);
}
return characters.size();
}
我还说,这也太简单了,难度怎么可能是中等呢。。。
确实没这么简单
2、我仔细审了审题,原来人家要的不只是不重复的所有字符的总和,而是所谓的最长的子串的长度。
比如最后一个例子,输入pwwkew,如果按照一开始的思路,那得到的就是pwke,长度是4。但是人家要的是wke,其实是3。也就是这个字符串必须是原来字符串中的一部分截出来的,不能跳,必须是连续的,不重复的。
那我想了想,也很快就来主意了,弄双层循环,第一层标记位从头部开始,到字符串尾部结束。第二层标记从第一层该层循环位置开始,也到字符串尾部结束。
然后弄两个set,第一个set记录每次在第二层循环内的数据,遇上不重复的就存进去,遇上重复的就从第二层循环跳出来。然后第一层继续往下走。这样如果该层得到的set长度比最终结果的set长的话,就给最终set替换成该层的set。
这样就会让程序从第一位开始一步一步往下走,验证每一个可能会出现的字符串是否符合条件。
private static Integer getLength(String s){
if (s == null || s.length() == 0) {
return 0;
}
//把字符串拆开逐个放到一个map中,如果有key
char[] charArray = s.toCharArray();
HashSet<Character> characters = new HashSet<>();
HashSet<Character> finalCharacters = new HashSet<>();
for (int i = 0; i < charArray.length; i++) {
for(int j = i; j < charArray.length; j++) {
if(!characters.contains(charArray[j])){
characters.add(charArray[j]);
}else{
break;
}
}
if(finalCharacters.size()<characters.size()){
finalCharacters.clear();
finalCharacters.addAll(characters);
}
characters.clear();
}
return finalCharacters.size();
}
我验证了一下,没有问题,通过!
但是,不够,人家最后一行还有小字提示。
0 <= s.length <= 5 * 10^4 s 由英文字母、数字、符号和空格组成
这个方法因为是双层循环,时间复杂度是O(n^2) 这样最大能干到25亿次操作,这也太。。。
3、说实话,我没想到更优解,我偷看了眼答案,精妙!!!
其实底层的逻辑是一样的,但是无需循环两次。
因为我的答案是有重复验证的过程的。比如abcabcbb,如果第一次循环是走到第四个位置,就是第二个a出现的时候。那么第二次循环是从b开始走,一直到第五个位置,也就是第二个b出现的位置,其中bca,也就是从第二个位置到第五个位置,都是我浪费掉的计算,因为这部分在第一个循环的时候已经验证过了,那么第二次循环就应该跳过这些步骤,直接接着第一次循环的位置开始走。也就是用一层循环即可,时间复杂度会一下从O(n^2)到O(n),质的飞跃
那么,这种方法就是滑动窗口,采用双指针的方法
private static Integer getLengthToN(String s){
if (s == null || s.length() == 0) {
return 0;
}
char[] charArray = s.toCharArray();
int left = 0;
HashSet<Character> finalCharacters = new HashSet<>();
HashSet<Character> characters = new HashSet<>();
for (int i = 0; i < charArray.length; i++) {
while (characters.contains(charArray[i])){
characters.remove(charArray[left]);
left++;
}
characters.add(charArray[i]);
if(finalCharacters.size()<characters.size()){
finalCharacters.clear();
finalCharacters.addAll(characters);
}
}
return finalCharacters.size();
}
4、还有优化的空间。。。
因为最后要得到的结果是最终字符串的长度,并不是要得到最终字符串所包含的字符,所以可以使用HashMap代替HashSet,用Map键存字符,值存其所处的位置。如果有重复的就收紧左侧。每次循环结束,看看当前字符的长度是不是比最终的长度长点,到最后循环完,即可得到最长的长度。
private static Integer getLengthBest(String s){
if (s == null || s.length() == 0) {
return 0;
}
char[] charArray = s.toCharArray();
int left = 0;
int result = 0;
Map<Character,Integer> characters = new HashMap<>();
for (int i = 0; i < charArray.length; i++) {
if(characters.containsKey(charArray[i])&& charIndexMap.get(currentChar) >= left){
left = charIndexMap.get(currentChar) + 1;
}
characters.put(charArray[i],i);
result = Math.max(result, i - left);
}
return result;
}