无重复字符的最长子串Java实现
今天碰到了无重复字符的最长子串这道题,看到了一个很巧妙的算法实现,整理一下自己学习后的想法和做题时的思路
思路:首先就看一下这个问题的状态空间是怎么样的,这个问题的目标是找到最长的那个子串的长度,那么最终的状态变化就是长度这个数值,而决定着长度的维度变量有2个下标,一个start一个end分别代表当前所见的字符串的起始位置和结束为止,我们在不断改变start和end的位置时寻找出最为标准的length进行保存。下面可以画出最原始的状态元组了(length,start,end)。
画了一个元组变化所生成的树的图
上面这棵树就描述了(length,start,end)的全部可能,在这些全部的可能中就存在一个状态他的length最大,那就是我们所要的最终结果和最终状态。
最原始的思路:就是对这个元组状态变化中所形成的树进行深度优先遍历,也就是以每一个start去不断增大end,记录最大length当end==str.length时,start++,end=start+1;根据测试这个方法大概会花费1000ms左右.
状态变化方程:if(start<str.length&&end<str.length)=>(length=max(length,end-start),start,end++)
if(start<str.length&&end==str.length)=>(length=max(length,end-start),start++,end=(start+1))
伪代码:
int start:=0,end:=0;
int result:=0;
map map;
for(start:=0..str.length){
for(end:=start..str.length){
if(map.hasKey(str[end])) {map.clear();break;}
map.put(str[end]);
result=max(result,end-start);
}
}
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.equals("")||s == null){
return 0;
}
if(s.length()==1){
return 1;
}
List<Character> list = new ArrayList<Character>(s.length());
int result = 0;
for(int i =0;i<s.length();i++){
for(int j = i;j<s.length();j++){
if(list.contains(s.charAt(j))){
list.clear();
break;
}else{
list.add(s.charAt(j));
}
if(result<j-i+1)
result = j-i+1;
}
}
return result;
}
}
可以很明显的看出来这个思路有很多重复的子树,接下来要做的就是对子树进行切除。
第二个思路:使用map来取代contains方法。根据测试这个方法大概会花费80到100ms
class Solution {
public int lengthOfLongestSubstring(String s) {
int start=0,end=0;
int max=0;
HashMap<Character,Integer> map = new HashMap<>();
char[] ch = s.toCharArray();
while(end<ch.length){
if(map.containsKey(ch[end])){
max = max>(end-start)?max:(end-start);
map.clear();
start = start+1;
end=start;
}else{
map.put(ch[end],1);
end++;
}
}
max = max>(end-start)?max:(end-start);
return max;
}
}
第三个思路就是在评论区的一个很巧妙的思路:这次我们++end而且不将end重制为start+1,通过一个128位的数组作为位图来快速找到以当前end结尾拥有最长length的子串的start的位置,如果我们能每一次都正确的找到最合适的start位置那么就可以再一次遍历中(end==0…str.length)将结果找出.
这个思路的难点就在于怎么能够有一个方法使我们可以一次就获取到最合适的start位置。代码采通过一个数组来存储每一个字符上一次所在的位置。
代码中通过start = Math.max(start, last[index] + 1);来确定最合适的start。这里的思路如下:
last[index] + 1代表:以当前位置所代表的字符上一次出现的位置+1,这就是以当前位置所代表字符为结尾最长的那个子串所有的长度;(例如:cabcda那么以最后一个a为结尾最长的那个子串就是上一个a所出现的位置+1而last[index]就代表着上一个a所出现的下标)
在下面贴出代码
class Solution {
public int lengthOfLongestSubstring(String s) {
// 记录字符上一次出现的位置
int[] last = new int[128];
for(int i = 0; i < 128; i++) {
last[i] = -1;
}
int n = s.length();
int res = 0;
int start = 0; // 窗口开始位置
for(int i = 0; i < n; i++) {
int index = s.charAt(i);
start = Math.max(start, last[index] + 1);//更新start
res = Math.max(res, i - start + 1);
last[index] = i;//更新记录
}
return res;
}
}