题目描述
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 :
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
解题思路:
根据给出的示例,首先将字符串通过toCharArray()
转化为字符数组,然后遍历数组元素并将其插入的List
集合中,同时判断集合中是否已经存在该字符。如果存在,则计算插入元素前的集合长度,然后将集合清空,将当前字符插入空白集合中,否则继续插入新的数组元素。最终最长的集合长度即为最长子串的长度。代码如下:
private static int lengthOfLongestSubstring(String s) {
// 创建一个int变量用于存储最长字符串的长度
int maxLength = 0;
// 创建一个list对象用于存储字符
List<Character> subString = new ArrayList<>();
// 将字符串转换为字符数组
char[] chs = s.toCharArray();
int length = 0;
// 遍历字符数组
for (char c : chs) {
// 判断当前集合是否包含当前字符
if (!subString.contains(c)) {
// 如果不包含则向集合中添加该字符
subString.add(c);
}else {
//否则,计算当前集合的长度,并清空集合后将该字符插入集合
length = subString.size();
subString.clear();
subString.add(c);
}
// 如果集合中只存在一个元素或者重复元素,则不会进入else语句,所以此处需要添加一个判断
length = (length >= subString.size()) ? length : subString.size();
// 每次循环完成后,将得到的子字符串长度和之前的maxLength作比较,如果大于则替换maxLength,反之则保持不变
maxLength = (length >= maxLength) ? length : maxLength;
}
// 返回最大子字符串长度
return maxLength;
}
运行以上代码,能通过测试用例,但提交代码却出现bug。经检查,发现代码中的subString.clear()
有问题,这里不应该将subString
清空,如字符串abcdbfg
,如果采用上面的代码,当读取到第二个b
时,调用clear()
就会将集合中的abcd
给删掉,最后的结果就是bfg
,而正确的结果是cdbfg
。正确的方式是在插入b
之前,将最后一个b
之前的元素给删掉。代码如下:
private static int lengthOfLongestSubstring(String s) {
int maxLength = 0;
List<Character> subString = new ArrayList<>();
char[] chs = s.toCharArray();
int length = 0;
for (char c : chs) {
if (!subString.contains(c)) {
subString.add(c);
}else {
length = subString.size();
// 获取插入元素c之前,最后一个c的下标
int index = subString.lastIndexOf(c) + 1;
// 将最后一个c之前的元素(包括c)删除,取subString的子集合
subString = subString(index,length);
subString.add(c);
}
length = length >= subString.size() ? length : subString.size();
maxLength = (length >= maxLength) ? length : maxLength;
}
return maxLength;
}
以上代码经测试,能通过abcdbfg
示例,但是提交后仍然出错,987个测试用例通过了986个,最后一个提示超出时间限制。测试上述代码,发现时间主要花在以下两行代码上。
// 使用这行代码比subString.clear()平均多花10ms左右
int index = subString.lastIndexOf(c) + 1;
// 而该行代码直接将运行时间从60ms拉长至200+,最终导致时间超长
subString = subString.subList(index, length);
经考虑,上面的代码是将重复字符前面的元素从subString
中删除从而取子集合,可以换一种思路,不用删除元素,而是将最后一次出现的重复元素的下标记录下来,然后和集合的length
相减,同样相当于得到子集合的长度。具体代码如下:
private static int lengthOfLongestSubstring(String s) {
int maxLength = 0;
List<Character> subString = new ArrayList<>();
char[] chs = s.toCharArray();
int index = 0, length = 0 ,lastIndex = 0;
for (char c : chs) {
if (!subString.contains(c)) {
subString.add(c);
}else {
// 记录元素c最后一次出现的下标,该下标只能往前不能往后
index = index > (lastIndex = subString.lastIndexOf(c)+1) ? index:lastIndex;
subString.add(c);
}
// 相当于计算子集合的长度
length = subString.size() - index;
maxLength = length >= maxLength ? length: maxLength;
}
return maxLength;
}
提交代码,可通过所有的测试用例,时间也控制在30ms左右,后续在思考更优的实现方法。