3.Longest Substring Without Repeating Characters
题目链接:https://leetcode.com/problems/longest-substring-without-repeating-characters/description/
题目大意:找出一串字符串的最长无重复子串。例子如下:
法一:两层for循环,一个字符一个字符的遍历其后是否有子串,且要判断是否不重复。这里有几个细节点,map的使用判断是否有重复,string.charAt(index)的使用摘出某一个字符。代码如下(耗时206ms):
1 public int lengthOfLongestSubstring(String s) { 2 int length = 1; 3 int max = 0; 4 for(int i = 0; i < s.length(); i++) { 5 length = 1; 6 Map<Character, Integer> map = new HashMap<Character, Integer>(); 7 map.put(s.charAt(i), i); 8 for(int j = i + 1; j < s.length(); j++) { 9 if(map.containsKey(s.charAt(j)) == false) { 10 //如果前面的字符串中没有当前字符,则将当前字符加入字符串中 11 length++; 12 map.put(s.charAt(j), j); 13 } 14 else { 15 //如果有,则直接退出从当前字符串起始位置的下一个位置开始重新计算字符串 16 //这里就有优化的地方了 17 break; 18 } 19 } 20 if(length > max) { 21 max = length; 22 } 23 } 24 return max; 25 }
法二(借鉴):一层for循环,用left记录子串起始位置,用set判断是否有重复,每走到下一个下标位置,判断当前存的子串中有没有当前字符,如果有,则根据left++将重复字符前面的包括它自己都从set中删去,也就是将重复字符+1作为新的子串的起始位置;如果没有,则将当前字符加入子串中。有点kmp的思想,这样只需要一次for循环即可。代码如下(耗时70ms):
1 public int lengthOfLongestSubstring(String s) { 2 int length = s.length(); 3 int left = 0, right = 0, ans = 0;//left记录子串开始下标,right记录子串结束下标 4 HashSet<Character> set = new HashSet<Character>();//set判断重复字符 5 while(right < length) { 6 if(set.contains(s.charAt(right)) == false) { 7 //前面的字符串中不包含当前字符 8 //将当前字符加入子串中,长度+1 9 set.add(s.charAt(right++)); 10 ans = Math.max(ans, set.size()); 11 } 12 else { 13 //前面的字符串中包含当前字符 14 //将重复字符前面的包括它自己都删去,也就是从重复字符+1开始重新计算子串 15 set.remove(s.charAt(left++)); 16 } 17 } 18 return ans; 19 }
法三(借鉴):法二需要遍历两遍,这个方法只需要遍历一遍,优化的地方在于:如果当前存的子串中有当前字符,则将left直接跳到重复字符下标+1处,而无需像法二一样从left处一个一个的往下跳。那么这里也就涉及到怎么保存重复字符下标的问题,很容易想到用map来做。的确用map可以解决,但是还有一点注意就是要跳到重复字符下标+1处,而不仅仅是跳到重复字符处,所以这里在何处+1应该谨慎,比如下面这样一个例子:
字符串“pwkkfw”:
第一趟:left = 0, right = 0, ans = right - left + 1 = 1, map(p, right + 1 = 1)
第二趟:left = 0, right = 1, ans = right - left + 1 = 2, map(p, 1)(w, right + 1= 2)
第三趟:left = 0, right = 2, ans = right - left + 1 = 3, map(p, 1)(w, 2)(k, right + 1 = 3)
第四趟:left = map(k).value = 3, right = 3, ans = 3, map(p, 1)(w, 2)(k, right + 1 = 4)
第五趟:left = 3, right = 4, ans = 3, map(p, 1)(w, 2)(k, 4)(f, right + 1 = 5)
第六趟:left = 3[这里不是2,因为要取大者,而且也取不到2,因为中间存在重复字符,取不到该字符串], right = 5, ans = 3, map(p, 1)(w, right + 1 = 6)(k, 4)(f, 5)
代码如下(耗时55ms):
1 public int lengthOfLongestSubstring(String s) { 2 Map<Character, Integer> map = new HashMap<Character, Integer>(); 3 int ans = 0; 4 int length = s.length(); 5 for(int left = 0, right = 0; right < length; right++) { 6 if(map.containsKey(s.charAt(right)) == true) { 7 //如果所存字符串中有当前字符,更新子串起始位置 8 //很容易犯错成left = map.get(s.charAt(right))+1,这里不能直接将最新值当成子串起始位置 9 //因为有可能这个最新值已经是过期值,即有可能是比当前left小的下标,而其后已经有重复字符不能取,比如pwkkfw字符串 10 //这里也不能犯错成left = Math.max(left, map.get(s.charAt(right))) 11 //因为要取到重复字符下标+1的位置,当然这里如果下面是map.put(s.charAt(right), right + 1)则就可以写成left = Math.max(left, map.get(s.charAt(right))) 12 left = Math.max(left, map.get(s.charAt(right))+1); 13 } 14 ans = Math.max(ans, right - left + 1); 15 map.put(s.charAt(right), right); 16 } 17 return ans; 18 }
法四(借鉴):思想相同,用int数组换掉map。代码如下(耗时39ms):
1 public int lengthOfLongestSubstring(String s) { 2 int ans = 0, length = s.length(); 3 int[] flag = new int[256]; 4 for(int left = 0, right = 0; right < length; right++) { 5 //这里不能用flag[s.charAt(right)+1,因为java默认初始化是0,这里如果+1,就会导致起始位置+1状态 6 //如果前面初始化为-1了,这里就可以+1,下面也可以改成flag[s.charAt(right)] = right 7 left = Math.max(left, flag[s.charAt(right)]); 8 ans = Math.max(ans, right - left + 1); 9 flag[s.charAt(right)] = right + 1; 10 } 11 return ans; 12 }