今天開始第三題,這個題目有點繞,我一開始都看錯了兩次題目,最後面才弄清楚究竟是要算什麼。我本身先是想了一下思路,用的方法雖然和網上大部分用的不太同樣,可是核心思想是同樣的(我想到的也是優化的滑動窗口,可是我使用的時StringBulider來存儲,沒有去使用map,list等,因此耗時更長),下面咱們就一塊兒來看看題目。ide
題目:給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。測試
示例1:輸入: "abcabcbb"優化
輸出: 3spa
解釋: 由於無重複字符的最長子串是 "abc",因此其長度爲 3。code
示例2:輸入: "bbbbb"blog
輸出: 1rem
解釋: 由於無重複字符的最長子串是 "b",因此其長度爲 1。字符串
示例3:輸入: "pwwkew"get
輸出: 3string
解釋: 由於無重複字符的最長子串是 "wke",因此其長度爲 3。
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
首先要明白這個題的意思,就是要咱們求字符串的不重複的連續子串,首先必須是連續的,不能斷開,其次子串中不能有一個字母是重複的。弄清楚了題目的意思後,讓咱們來看看解法。
1)暴力法:暴力法老是最容易讓人明白的方法,首先咱們拿到字符串的全部子串,這個經過兩次循環能夠實現拿到全部字串的區間,這裏咱們採用[i,j)(左閉右開)的區間。而後咱們實現一個方法,這個方法用來判斷傳入的字符串在一段區間內是否存在重複,這個能夠藉助set來實習。假設這段區間內的字串沒有重複字符,那麼咱們就要去對比更新最大字串的值。代碼以下:
1 public class Solution { 2 public int lengthOfLongestSubstring(String s) { 3 int sum = 0; 4 int length = s.length(); 5 for(int i = 0; i < length; i++){ 6 for(int j = i + 1; j <= length; j++){ 7 sum = uniqueStr(s, i , j) && sum < j - i ? sum = j - i : sum; 8 } 9 } 10 return sum; 11 } 12 13 public boolean uniqueStr(String s, int i, int j){ 14 Set<Character> set = new HashSet(); 15 for(int n = i; n < j; n++){ 16 if(set.contains(s.charAt(n))){ 17 return false; 18 } 19 set.add(s.charAt(n)); 20 } 21 return true; 22 } 23 }
2)2.1 滑動窗口法:這裏咱們要引入一個概念---窗口,窗口其實就是相似於上面提到的左閉右開的一個區間。那麼爲何叫作滑動窗口呢?你們能夠看到,上面的暴力法,咱們作了不少無用的計算,在區間[i,j)中,假如咱們判斷是存在重複字符,那麼以i開頭,而且結尾大於j的全部區間都是存在重複字符的。舉個例子,在【0,5)這個區間是存在重複字符的,那麼在【0,6),【0,7)...一直到末尾的全部區間都會是存在重複字符的。這個時候咱們就不必進行判斷了,能夠直接進行i+1的操做,因此你們會發現,每次操做要麼j+1,要麼i+1,整個區間就像是在滑動同樣,因此就要滑動窗口。代碼以下:
1 public class Solution { 2 public int lengthOfLongestSubstring(String s) { 3 int sum = 0; 4 int length = s.length(); 5 int begin = 0; 6 int end = 0; 7 while(begin < length && end < length){ 8 if(uniqueStr(s, begin, ++end)){ 9 sum = sum < end - begin ? end - begin : sum; 10 }else{ 11 begin++; 12 } 13 14 } 15 return sum; 16 } 17 18 public boolean uniqueStr(String s, int i, int j){ 19 Set<Character> set = new HashSet(); 20 for(int n = i; n < j; n++){ 21 if(set.contains(s.charAt(n))){ 22 return false; 23 } 24 set.add(s.charAt(n)); 25 } 26 return true; 27 } 28 }
2.2 上面的滑動窗口法,雖然已經能夠經過測試了,可是仍是耗費的時間比較久。由於若是在【i,j)上是沒有重複字符的,在計算j位置的時候,咱們只須要計算j是否在【i,j)上存在就行,因而咱們可使用一個全局的set來替代這個滑動窗口,讓檢查是否重複變成O(1)的操做,在區間左邊界發生變化的時候,咱們只須要移除set中begin所指向的字符,從而達到滑動窗口到效果。舉個例子,咱們已經判斷了在【0,5)這個區間是沒有重複字符的,在判斷【0,6)這個區間的時間,又要一個一個加入到set來進行判斷,這樣就佔用了大量時間,若是用一個全局set保存的話,第5個字符進來,咱們只須要在set中判斷一下是否存在就好了。代碼以下:
1 public class Solution { 2 public int lengthOfLongestSubstring(String s) { 3 int sum = 0; 4 int length = s.length(); 5 int begin = 0; 6 int end = 0; 7 Set<Character> set = new HashSet(); 8 while(begin < length && end < length){ 9 if(!set.contains(s.charAt(end))){ 10 set.add(s.charAt(end++)); 11 sum = sum < end - begin ? end - begin : sum; 12 }else{ 13 set.remove(s.charAt(begin++)); 14 } 15 } 16 return sum; 17 } 18 }
2.3 優化版滑動窗口:其實有人可能已經發現了,還能夠繼續優化。當咱們在【i,j)區間是無重複字符區間,檢測到第J個字符在這個區間是已經存在的,假設重複的位置是K(i <= k < j),那麼其實咱們能夠直接將窗口跳至【k+1,j),而不是【i+1,j)。舉個例子,若是字符串是「abcdhcef」,咱們在【0,5)這個區間上並無發現字符重複,而當第5個字符進行判斷時,咱們會發現c這個字符已經存在,若是咱們向以前那樣,將區間的左邊界進行加1,變成【1,5),再將第五個元素進行判斷,其實仍是重複的,假設咱們找到c以前存在到位置2,而後之間將窗口滑動到【2,5),這樣就大量節省了時間。這時咱們不單單須要記錄字符串,還須要記錄其對應的位置,因此咱們須要使用一個map來保存,代碼以下:
1 public class Solution { 2 public int lengthOfLongestSubstring(String s) { 3 //字符串的長度 4 int n = s.length(); 5 //定義最大字串長度sum 6 int sum = 0; 7 //建立一個map 8 Map<Character, Integer> map = new HashMap<>(); 9 for(int begin = 0, end = 0; end < n; end++) { 10 //獲取end位置的字符位置 11 Integer index = map.get(s.charAt(end)); 12 //若是爲空,表示以前不存在重複字符 13 //若是不爲空,則要判斷字符的位置是否大於等於begin的位置,由於咱們的區間是【begin,end) 14 //而咱們進行窗口滑動時候,只是將begin移動到了重複字符以後,並無在map中刪除重複字符以前到全部元素 15 //因此,咱們要判斷重複的字符是否在咱們有效的區間內 16 if (index != null && index >= begin) { 17 //區間內重複,則將begin滑動到重複字符的下一個元素 18 begin = index + 1; 19 } 20 //將字符串放入map,或更新它的位置 21 map.put(s.charAt(end), end); 22 //計算sum 23 sum = Math.max(sum, end - begin + 1); 24 } 25 return sum; 26 } 27 }
2.4 由於map在滑動窗口左邊界移動時,很差刪除左邊界以前的元素,因此這個窗口須要咱們從邏輯上理解,爲了更直觀的簡直,咱們還可使用LinkedList來替代map,代碼以下:
1 class Solution 2 { 3 public int lengthOfLongestSubstring(String s) 4 { 5 int sum = 0;//記錄最長子串長度 6 LinkedList<Character> temp = new LinkedList<>(); 7 8 for (int i = 0; i < s.length(); i++ ) 9 { 10 Character curCh = s.charAt(i); 11 if (!temp.contains(curCh)){ 12 temp.add(curCh); 13 sum = sum < temp.size() ? temp.size() : sum; 14 } 15 else//若是新增字符與原子串中字符有重複的,刪除原子串中重複字符及在它以前的字符,與新增字符組成新的子串 16 { 17 int index = temp.indexOf(curCh); 18 for (int j = 0;j <= index;j++ ){ 19 temp.remove(); 20 } 21 temp.add(curCh); 22 } 23 } 24 return sum; 25 } 26 }
熬夜寫完了這篇文章,上面的方法可能還不是最優的,若是你們有什麼疑問或者有更好的方法,歡迎留言討論,謝謝!