无重复字符的最长子串
题目:
首先,先用了一个很蠢的算法,就是直接暴力求解。
用两个循环,外层循环提供子串开始字符,内层循环一直加一,直到出现重复字符退出。重复字符的计数用了哈希表的一点思想,直接将字符ASCII码作为索引下标,时间复杂度是O(n^2)。
public class Longest{
public static void main(String[] args) {
String s = new String("abcabcbb");
System.out.println(lengthOfLongestSubstring(s));
}
public static int lengthOfLongestSubstring(String s){
int res=0;
//int hold=0;
for(int i=0;i<s.length();i++){
boolean[] arr=new boolean['z'+1];//没被选就是false
int j=i;
while(j<s.length()&&arr[s.charAt(j)]!=true){
arr[s.charAt(j)]=true;
j++;
}
if(j-i>res)res=j-i;
}
return res;
}
}
最后效果是真滴想吐槽。。。你们设计得也太慢了点吧(或者说他这个排名是所有语言统一的?我占了java的便宜?但是C++理论上比java快个四五倍吧)
改进
如何改进我是这样想的,实际上,中间有很多是没有必要算的,即一个无重复子串中,从第一个到被退出的那个字符间,之后是没必要再遍历了。如,“rbcadabefg”中,第一个符合条件的子串即rbcad,其中从r到a之后都不用再遍历了,下一次找符合的子串的开头从d开始就可以了。这有点动态规划的意思在里面。基于这样的思想,我就这样写了:
public static int lengthOfLongestSubstring(String s){
int res=0;
//int hold=0;
for(int i=0;i<s.length();){
int[] arr=new int[128];//没被选就是-1
for(int t=0;t<128;t++)arr[t]=-1;
int j=i;
while(j<s.length()&&arr[s.charAt(j)]==-1){
arr[s.charAt(j)]=j;
j++;
}
if(j-i>res)res=j-i;
if(j>=s.length()-1)break;
i=arr[s.charAt(j)]+1;
}
return res;
}
然后。。。令我吃惊的是。。。。
???WHAT THE MOTHER FUCKER????
后来我在评论里面找到了一位大佬的做法
感觉基本思路大同小异
public static int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) {
return 0;
}
// 用map来保存每个字符的位置,初始时为-1
int[] map = new int[128];
for (int i = 0; i < 128; i++) {
map[i] = -1;
}
// i是当前位置,start是当前子字符串的起始位置
int start = 0, max = 1, i = 0, index;
for (; i < s.length(); i++) {
// 将字符转为数字作为数组索引
index = (int) s.charAt(i);
// 如果当前字符在数组中存在且位置不小于起始位置,说明此字符重复
// 更新最大长度,将起始位置挪到此位置之后
if (map[index] >= start) {
max = Math.max(i - start, max);
start = map[index] + 1;
}
// 更新当前字符的位置
map[index] = i;
}
max = Math.max(i - start, max);
return max;
}
但是实际效果大不一样
认真读了别人的代码之后,发现大佬比我牛的地方在于,大佬只用了一次循环,只初始化了一次数组。实际上大佬的复杂度只有O(n),而我有着大量多余的设定。
在深入的推测下大佬是怎么想到的。
很像前面的“最长升序子集问题”,核心思想就是,从第一个字符开始,找到当前状态的最大子串,然后往后面依次挪动,顺理成章的就有后面一个字符符合条件与否的判断。
还有,就是我的代码写得是真的丑,
以后一定要尽可能漂亮一点。。。。