给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
—— 原文摘自:力扣(LeetCode | https://leetcode-cn.com/problems/longest-substring-without-repeating-characters)
看到这道题,我第一时间排除了双重循环的暴力解法,因为时间复杂度最起码都在 O(n2)。但是呢,我自己写的也不咋地
class Solution {
public int lengthOfLongestSubstring(String s) {
int maxLength = 0;
for (int i = 0; i < s.length(); i++) {
List<Character> characters = new ArrayList<>();
for (int j = i; j < s.length(); j++) {
if (characters.contains(s.charAt(j))) {
break;
}
characters.add(s.charAt(j));
maxLength = Math.max(characters.size(), maxLength);
}
}
return maxLength;
}
}
由于我第一时间排除掉了暴力破解,所以我没有自己写暴力法,从众多大佬的题解中找了一个试了下,吓死宝宝了
class Solution {
public int lengthOfLongestSubstring(String s) {
int maxLen = 0;
int tmpLen = 0;
int maxRemoved = -1;
Map<Character, Integer> map = new HashMap<>();
for(int i = 0; i < s.length(); i++) {
char current = s.charAt(i);
if(map.containsKey(current)) {
int repeatedIndex = map.get(current);
if(repeatedIndex - maxRemoved == 1) {
for(int j = repeatedIndex; j > maxRemoved; j--) {
map.remove(current);
}
tmpLen = i - repeatedIndex;
map.put(current, i);
maxRemoved = repeatedIndex;
} else {
maxLen = Math.max(tmpLen, maxLen);
for(int j = repeatedIndex; j > maxRemoved; j--) {
map.remove(s.charAt(j));
}
map.put(current, i);
maxRemoved = repeatedIndex;
tmpLen = i - maxRemoved;
}
} else {
map.put(current, i);
tmpLen++;
}
}
maxLen = tmpLen > maxLen ? tmpLen : maxLen;
return maxLen;
}
}
当时写的时候也没有想太多,只是想的最起码自己完整实现一个算法啊,于是就硬着头皮写了下来,最终也通过了所有测试案例。但是,性能并不是很好,而且可以看出来,相当复杂。
不过呢,事后再看了一遍我实现的这个算法,其实思想是对的,只是走了一些弯路,而且还有改进的空间,不过,也没继续往下改!
关于滑动窗口解法,我自己并没有想出来,而是参考评论区中的各位大佬的,但是,由于每个人都有自己的思路,所以写出的代码并不尽相同,所以我仅仅只列出我参考的几个算法,并将我自己参考并自己最终写出的代码列出来。
参考
改动后我的题解
class Solution {
public int lengthOfLongestSubstring(String s) {
int maxLen = 0;
// 记录字符串中以每个字符为子串首字符时的子串开始位置;利用char和int相互转换的特性,相当于一个map,128为常用ASCII码字符表长度
int[] last = new int[128];
// 最长(结果)子串的开始位置,初始值为0
int start = 0;
for(int i = 0; i < s.length(); i++) {
// 获取当前字符,将其转换为int,这样就不用使用map了
int current = s.charAt(i);
// 如果当前字符以前出现过,则更换子串,对新的子串进行运算
start = Math.max(start, last[current]);
// 计算出当前子串的长度,与上一次记录的最长子串的长度进行比较
maxLen = Math.max(maxLen, i - start + 1);
// 记录:如果以当前字符为子串首字符,那么对应子串的开始位置
last[current] = i + 1;
}
return maxLen;
}
}
虽然没有看完所有人的题解,但是从这道题中学到了一些东西的,感觉受益颇多!