Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the length is 3. For "bbbbb" the longest substring is "b", with the length of 1.
给定一个字符串,从里面找出最长的而且不含重复字符的子串,然后输出该子串的长度。
很久不接触这类题,做起来挺吃力的。
一如既往地先上最直接的方法:将字符串每一位都存入HashMap,当存在相同的字符时,取HashMap的长度与之前所记录的子串长度的最大值,然后清空HashMap,并且重新从当前子串中的相同字符的位置开始循环以上步骤。
public class Solution {
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> hashMap = new HashMap<>();
int size = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (hashMap.containsKey(c)) {
if (size < hashMap.size()) {
size = hashMap.size();
}
i = hashMap.get(c);
hashMap.clear();
}
else {
hashMap.put(c, i);
}
}
if (size < hashMap.size()) {
size = hashMap.size();
}
return size;
}
}这段代码时间复杂度为O(n^3),当字符串的长度比较小的时候还可以,但是当字符串长度增长之后就很麻烦了。LeetCode给了一个三万多的字符串过来,当场就Time Limit Exceeded了。
那么接下来就是优化了。查找了一下资料,突然想起char是以ASCII表取值的,算上所有字符共256个,而我们的目的是要判断一个字符是否在当前子串是唯一的。所以思路如下:
1、记录当前子串的起始点startIndex , maxSize为记录下来的子串最大长度
2、for循环[for( int i = 0 ; i < s.length() ; i++)],当判断到有相同字符时,首先记录当前子串的长度,与maxSize比较取最大值;然后将起始点startIndex设置为当前子串中相同字符位置的后一位
3、重复步骤二直到循环结束
举个例子,以字符串“abcabcbb”为例。
经过for循环,当 i = 3 时,情况如下
此时,代码中判断到第4个字符与当前子串的中第一个字符相同。
因此,首先计算当前子串长度为 : i - startIndex = 3
然后比较并取最大值保存到maxSize中 : maxSize = Math.max(maxSize , i - startIndex)
最后,将startIndex置为当前子串中相同字符串的后一位,如下图
就这样,一直循环重复上述步骤,到最后就能得出结果。这样一来,时间复杂度大致是O(n)。
因为char取整型时数值范围为0~255,而且为了方便获取相同字符所在的Index,我用一个长度为256的int数组来保存所有字符的index。首先把数组中所有项都设为-1,如果当前char在数组中所对应的值大于-1,则说明该字符已经存在过;否则,将改字符的index存进去。
public class Solution {
public int lengthOfLongestSubstring(String s) {
int[] index = new int[256];
int size = 0;
int start = 0;
int len = s.length();
for (int i = 0; i < 256; i++) {
index[i] = -1;
}
for (int i = 0; i < len; i++) {
int c = s.charAt(i);
if (index[c] != -1) {
int indexTemp = index[c];
for (int j = start; j < indexTemp + 1; j++) {
index[s.charAt(j)] = -1;
}
int temp = i - start;
size = temp > size ? temp : size;
start = indexTemp + 1;
index[c] = i;
}
else {
index[c] = i;
}
}
int temp = len - start;
size = temp > size ? temp : size;
return size;
}
}
要注意的是要将已存在的字符之前的所有字符都设置为-1
这段代码的RunTime为6ms~7ms
而另外在网上看到一段更优的代码,思路是一样的,但是更加简洁方便
public int lengthOfLongestSubstring(String s) {
if(s==null)
return 0;
boolean[] flag = new boolean[256];
int result = 0;
int start = 0;
char[] arr = s.toCharArray();
for (int i = 0; i < arr.length; i++) {
char current = arr[i];
if (flag[current]) {
result = Math.max(result, i - start);
// the loop update the new start point
// and reset flag array
// for example, abccab, when it comes to 2nd c,
// it update start from 0 to 3, reset flag for a,b
for (int k = start; k < i; k++) {
if (arr[k] == current) {
start = k + 1;
break;
}
flag[arr[k]] = false;
}
} else {
flag[current] = true;
}
}
result = Math.max(arr.length - start, result);
return result;
}这段代码的RunTime为 4ms~5ms
我的代码和这段代码相比,一是之前的int数组初始化多耗费了时间,二是boolean数组比int数组要节省空间。
为此,我又优化了一下代码,直接用int数组的默认值0判断是否存在相同字符,为此,需要将字符index全部+1。优化后的代码RunTime为5ms。而这段代码就不放出来了,因为index全部加一之后反而不好理解了。
813

被折叠的 条评论
为什么被折叠?



