題目連接:https://leetcode.com/problems/longest-substring-without-repeating-characters/description/java
題目大意:找出一串字符串的最長無重複子串。例子以下:數組
法一:兩層for循環,一個字符一個字符的遍歷其後是否有子串,且要判斷是否不重複。這裏有幾個細節點,map的使用判斷是否有重複,string.charAt(index)的使用摘出某一個字符。代碼以下(耗時206ms):ide
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應該謹慎,好比下面這樣一個例子:spa
字符串「pwkkfw」:code
第一趟:left = 0, right = 0, ans = right - left + 1 = 1, map(p, right + 1 = 1)blog
第二趟:left = 0, right = 1, ans = right - left + 1 = 2, map(p, 1)(w, right + 1= 2)ip
第三趟:left = 0, right = 2, ans = right - left + 1 = 3, map(p, 1)(w, 2)(k, right + 1 = 3)leetcode
第四趟:left = map(k).value = 3, right = 3, ans = 3, map(p, 1)(w, 2)(k, right + 1 = 4)rem
第五趟: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 }