給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。java
示例 1:bash
輸入: "abcabcbb" 輸出: 3 解釋: 由於無重複字符的最長子串是 "abc",因此其長度爲 3。
示例 2:測試
輸入: "bbbbb" 輸出: 1 解釋: 由於無重複字符的最長子串是 "b",因此其長度爲 1。
示例 3:優化
輸入: "pwwkew" 輸出: 3 解釋: 由於無重複字符的最長子串是 "wke",因此其長度爲 3。 請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
這道題的目標是找出最長子串,而且該子串必須不包含重複字符,並且這個子串必須是原字符串中連續的一部分(見示例3中的解釋說明)。code
拿到題目時先不要心急想什麼騷操做,咱們先從最普通的操做開始把題目解出來,而後再來看如何優化。blog
接下來,咱們畫圖分析一下,先隨便弄一個長相普通的字符串:frankissohandsome
,咱們要從中找出咱們想要的子串,那少不了須要遍歷,咱們設置兩個變量from
,to
,分別存儲尋找的目標子串在原字符串中的首尾位置。字符串
首先,from
和to
的初始值都爲0(String的序號從0開始),子串長度length = 1
,最大子串長度maxLength = 1
。string
而後,咱們將to
的指向日後移動,並判斷新遍歷的字符是否已經存在於子串中,若是不存在,則將其加入子串中,並將length
進行自增。it
直到找到一個已存在於子串中的字符,或者to
到達字符串的末尾。這裏,咱們找到了一個重複的s
,序號爲7
,此時的子串爲frankis
,將此時的子串長度與最大子串長度相比較(目前爲0
),若是比最大子串長度大,則將最大子串長度設置爲當前子串長度7
。io
接下來,咱們繼續尋找符合條件的子串,這裏比較關鍵的一點是下一個子串的起始位置,這裏咱們將from
直接跳到了序號爲7
的位置,由於包含ss
的子串顯然都不能知足要求。
而後咱們依照以前的方法,找到第二個候選的子串sohand
,長度爲6
,比目前的最大子串長度小,因此不是目標子串。
接着繼續尋找,找到另外一個候選子串ohands
,長度小於最大子串長度,不是咱們的目標子串。
繼續尋找。
to
到達了字符串末尾,找到另外一個候選子串handsome
,長度大於最大子串長度,這就是咱們的目標子串。
因而咱們的最大子串長度就輕鬆加愉快的找到了。接下來的事情就是把上面的思路轉化成代碼。
這裏只須要注意一下from
的跳轉便可,每次跳轉的序號爲to
指向的字符在子串中出現的位置 + 1。
class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; int from = 0, to = 1, length = 1, maxLength = 1; // to遍歷直到字符串末尾 while (to < s.length()){ int site = s.substring(from, to).indexOf(s.charAt(to)); if (site != -1){ // to指向的字符已存在 length = to - from; if (length > maxLength) maxLength = length; // from 跳轉到site+1的位置 from = from + site + 1; } to++; } // 處理最後一個子串 if (to - from > maxLength) { maxLength = to - from; } return maxLength; } }
這裏沒有什麼騷操做,考慮好邊界狀況就好了。有一個小細節須要注意,site
表明的是子串中字符出現的位置,不是原字符串中的位置,所以from
在跳轉時,須要加上自身原來的序號。還有最後一個子串的處理不要忘記,由於當to
遍歷到字符串末尾時,會結束循環,最後一個子串將不會在循環內處理。
讓咱們提交一下:
擊敗了73%
的用戶,還不錯。
想一想看,還有沒有優化的空間呢?
那確定是有的,首先咱們想想,當咱們找到的最大子串長度已經比from
所在位置到字符串末尾的位置還要長了,那就沒有必要再繼續下去了。
class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; int from = 0, to = 1, length = 1, maxLength = 0; // to遍歷直到字符串末尾 while (to < s.length()){ int site = s.substring(from, to).indexOf(s.charAt(to)); if (site != -1){ // to指向的字符已存在 length = to - from; if (length > maxLength) { maxLength = length; } // 判斷是否須要繼續遍歷 if (maxLength > s.length() - from + 1) return maxLength; from = from + site + 1; } to++; } // 處理最後一個子串 if (to - from > maxLength) { maxLength = to - from; } return maxLength; } }
另外要處理相似bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
這樣的字符串,上面的方法仍是有很大優化空間的,咱們能夠用一個HashSet
來存儲全部元素,利用其特性進行去重,若是找到的子串長度已經等於HashSet
中的元素個數了,那就不用再繼續查找了。
class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; int from = 0, to = 1, length = 1, maxLength = 0; Set<Character> set = new HashSet<>(); for (int i = 0; i < s.length(); i++){ set.add(s.charAt(i)); } // to遍歷直到字符串末尾 while (to < s.length()){ int site = s.substring(from, to).indexOf(s.charAt(to)); if (site != -1){ // to指向的字符已存在 length = to - from; if (length > maxLength) { maxLength = length; } if (maxLength > s.length() - from + 1) return maxLength; if (maxLength >= set.size()) return maxLength; from = from + site + 1; } to++; } // 處理最後一個子串 if (to - from > maxLength) { maxLength = to - from; } return maxLength; } }
再提交一下:
哈哈哈哈,翻車了,因此這裏引入一個HashSet
用空間來換時間的方式不必定合適,看來測試用例裏像bbbbbbbbbbbbbb
這樣的用例並很少啊。
那麼今天的翻車就到此爲止了,若是以爲對你有幫助的話記得點個關注哦。