1. 题目描述
2. 解题思路
看到这条题目的瞬间脑海中就漂浮了KMP算法的身影,KMP算法是解决子串匹配问题,而这里是寻找最长子串,多少有点相似,但其实这道题目与KMP算法没什么关系。
首先,这道题目的简单解法是可以利用嵌套循环来解决的,利用嵌套循环就可以找出以该字符串中的每一个字符作为开头的子串长度分别的多少,显然这种做法时间复杂度太高了,没有实际意义,我们应该寻找一下线性时间复杂度的解法。既然是线性时间复杂度,那么意味着只能遍历一次字符串(或其他线性次),换言之我需要“记住”之前出现过的字符,记忆可以通过哈希表实现,接下来我们来用例子解释一下如何进行。
例如对于字符串“abcabcbb”,当我遍历到第二个a时,由于a已经存在于哈希表当中,那就表明现在的最长子串是“abc”,长度为3,接下来我们应该在“abcbb”中寻找比3大的子串吗?不对,其实应该是在“bcabcbb”中寻找比3大的子串。分析到这里,可能第一直觉就是,寻找到了一个重复的字符后就把搜索区域向前移一位,但其实不是这样的,对于“abcbbaca”,当我遍历到第二个b时,我应该在“bcbbaca”中寻找比3大的子串吗?显然不是,我们应该在“cbbaca”中寻找比3大的子串,那就是说并不是找到一个重复字符后就把搜索区域向前移一位,而是需要根据该重复字符的出现位置所决定的。上述过程我把它称为动态划分区域,这种方法只需要遍历字符串一遍即可得出最长子串的长度。
可以对这个算法进行更一步的改进吗?是可以的,在上述算法的实现中,从字符串中取出字符的操作是通过charAt()
实现的,而我在别人的刷题经验中得知,先把字符串转换成字符数组再通过字符数组取字符是可以优化时间性能的。这是改进版一的改进点。
其实还有没有进一步的优化空间呢?是有的,在之前的做题经验中发现,在条件允许的情况下(元素个数已知),使用数组代替哈希表能够大幅度提升时间性能。而在这道题目中,我们明确知道该字符串中的字符都是ASCII表上的字符,总共有128位,因此我们可以创建一个长度为128的数组代替哈希表。这就是改进版二的改进点。
3. 代码实现
3.1 动态划分区域
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> map = new HashMap<>();
int max = 0, count = 0,index=-1;
for (int i = 0; i < s.length(); i++) {
if(map.getOrDefault(s.charAt(i),-1)>index){
max = Math.max(count, max);
index = map.get(s.charAt(i));
count = i - index;
map.put(s.charAt(i), i);
} else {
map.put(s.charAt(i), i);
count++;
}
}
return Math.max(count, max);
}
3.2 改进版一
public int lengthOfLongestSubstring(String s) {
int[] memory = new int[128];
Arrays.fill(memory, -1);
int max = 0, count = 0, index = -1;
for (int i = 0; i < s.length(); i++) {
if (memory[s.charAt(i)] > index) {
max = Math.max(count, max);
index = memory[s.charAt(i)];
count = i - index;
memory[s.charAt(i)] = i;
} else {
memory[s.charAt(i)] = i;
count++;
}
}
return Math.max(count, max);
}
3.3 改进版二
public int lengthOfLongestSubstring(String s) {
int[] memory = new int[128];
Arrays.fill(memory, -1);
char[] chars = s.toCharArray();
int max = 0, count = 0, index = -1;
for (int i = 0; i < chars.length; i++) {
if (memory[chars[i]] > index) {
max = Math.max(count, max);
index = memory[chars[i]];
count = i - index;
memory[chars[i]] = i;
} else {
memory[chars[i]] = i;
count++;
}
}
return Math.max(count, max);
}
3.4 对比
上述三种算法的时间复杂度都是O(n),只需要遍历一次字符串,而算法间的实际性能表现差异较大,主要原因还是取决于哈希表与普通数组的存储所使用的时间不同;对于空间复杂度而言,三种算法实际上都是O(1),因为其存储的变量最大不超过128个。