給定一個字符串,請你找出其中不含有重復字符的 最長子串 的長度。
示例 1:
輸入: "abcabcbb"
輸出: 3
解釋: 因為無重復字符的最長子串是 "abc",所以其長度為 3。
示例 2:
輸入: "bbbbb"
輸出: 1
解釋: 因為無重復字符的最長子串是 "b",所以其長度為 1。
示例 3:
輸入: "pwwkew"
輸出: 3
解釋: 因為無重復字符的最長子串是 "wke",所以其長度為 3。
? ? 請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
暴力求解
我們大腦一眼就能看到一個字符串中哪些字符是重復的,按照計算機的思路來分析,首先從首字符開始遍歷,如果發(fā)現(xiàn)后面的字符不和首字符相同,那么把首字符和后面的字符作為一個整體,再依次和后面進行判斷,如果發(fā)現(xiàn)下一個字符是在首字符串里面包含的,那么把首字符串作為單獨的一段,放到set集合里面,再從下一個字符重復如上的操作,最后比較出set集合里面最長的字符串,就是答案,代碼如下:
publicintlengthOfLongestSubstring(Strings) {
intlen=s.length();
HashSet<String>set=newHashSet<>();
intres=1;
if(len<=1) {
res=len;
? ? ?? }
for(inti=0;i<len;i++) {
Stringch=s.charAt(i)+"";
for(intj=i+1;j<len;j++) {
if(!ch.contains(s.charAt(j)+"") ){
ch+=s.charAt(j);
if(set.contains(ch)){
res=Math.max(ch.length(),res);
}else{
set.add(ch);
? ? ? ? ? ? ? ? ?? }
}else{
if(set.contains(ch)){
res=Math.max(ch.length(),res);
}else{
set.add(ch);
? ? ? ? ? ? ? ? ?? }
break;
? ? ? ? ? ? ?? }
? ? ? ? ?? }
?
? ? ?? }
Iteratoriterator=set.iterator();
while(iterator.hasNext()) {
res=Math.max(iterator.next().toString().length() ,res);
? ? ?? }
returnres;
?? }
暴力求解算法分析
如圖所示,此算法的并不是最優(yōu)解

因為用了兩次for循環(huán),如果有n個字符,那么比較n*(n-1)次,也就是說時間復雜度是O(n2)
那么有什么更優(yōu)的解法嗎?
滑動窗口
思路
使用一個for循環(huán),依次遍歷每個字符,累加到一個字符串中,在每次遍歷的時候比較該字符在字符串中是否出現(xiàn)過,如果出現(xiàn),把該字符串放入set集合中,接下來再根據(jù)該字符再字符串中出現(xiàn)的位置不同,根據(jù)出現(xiàn)的位置不同,分情況處理,最后比較set集合中最大的字符串長度。
代碼
publicintlengthOfLongestSubstring(Strings) {
intres=0;
HashSet<String>set=newHashSet<>();
Stringcurrent="";
for(inti=0;i<s.length() ;i++) {
if(current.contains(s.charAt(i)+"") ){
set.add(current);
intindex=current.indexOf(s.charAt(i)) ;
? ? ? ? ? ? if(index==0){
//出現(xiàn)在第一個
current=current.substring(1,current.length());
i-=1;
}elseif(index==current.length()-1){
//出現(xiàn)在最后一個
current=s.charAt(i)+"";
}else{
//出現(xiàn)在中間
current=current.substring(index+1,current.length());
current+=s.charAt(i);
System.out.println(current);
? ? ? ? ?? }
}else{
current+=s.charAt(i)+"";
if(i==s.length()-1)set.add(current);
? ? ?? }
?? }
Iteratoriterator=set.iterator();
while(iterator.hasNext()) {
res=Math.max(iterator.next().toString().length() ,res);
?? }
returnres;
}
結(jié)果

分析
乍一看確實只有一次for循環(huán),但是因為指針往往需要回退,所以實際的時間復雜度到大于等于O(n)小于等于O(n2)
官方答案:滑動窗口
思路和算法
我們先用一個例子來想一想如何在較優(yōu)的時間復雜度內(nèi)通過本題。
我們不妨以示例一中的字符串 \texttt{abcabcbb}abcabcbb 為例,找出 從每一個字符開始的,不包含重復字符的最長子串,那么其中最長的那個字符串即為答案。對于示例一中的字符串,我們列舉出這些結(jié)果,其中括號中表示選中的字符以及最長的字符串:
以 {(a)bcabcbb}(a)bcabcbb 開始的最長字符串為 {(abc)abcbb}(abc)abcbb;
以 {a(b)cabcbb}a(b)cabcbb 開始的最長字符串為 {a(bca)bcbb}a(bca)bcbb;
以 \{ab(c)abcbb}ab(c)abcbb 開始的最長字符串為 {ab(cab)cbb}ab(cab)cbb;
以 {abc(a)bcbb}abc(a)bcbb 開始的最長字符串為 {abc(abc)bb}abc(abc)bb;
以 {abca(b)cbb}abca(b)cbb 開始的最長字符串為 {abca(bc)bb}abca(bc)bb;
以 {abcab(c)bb}abcab(c)bb 開始的最長字符串為 {abcab(cb)b}abcab(cb)b;
以 {abcabc(b)b}abcabc(b)b 開始的最長字符串為 {abcabc(b)b}abcabc(b)b;
以 {abcabcb(b)}abcabcb(b) 開始的最長字符串為 {abcabcb(b)}abcabcb(b)。
發(fā)現(xiàn)了什么?如果我們依次遞增地枚舉子串的起始位置,那么子串的結(jié)束位置也是遞增的!這里的原因在于,假設(shè)我們選擇字符串中的第 kk 個字符作為起始位置,并且得到了不包含重復字符的最長子串的結(jié)束位置為 r_kr k 。那么當我們選擇第 k+1k+1 個字符作為起始位置時,首先從 k+1k+1 到 r_kr k 的字符顯然是不重復的,并且由于少了原本的第 kk 個字符,我們可以嘗試繼續(xù)增大 r_kr k ,直到右側(cè)出現(xiàn)了重復字符為止。
這樣以來,我們就可以使用「滑動窗口」來解決這個問題了:
我們使用兩個指針表示字符串中的某個子串(的左右邊界)。其中左指針代表著上文中「枚舉子串的起始位置」,而右指針即為上文中的 r_kr k ;在每一步的操作中,我們會將左指針向右移動一格,表示 我們開始枚舉下一個字符作為起始位置,然后我們可以不斷地向右移動右指針,但需要保證這兩個指針對應的子串中沒有重復的字符。在移動結(jié)束后,這個子串就對應著 以左指針開始的,不包含重復字符的最長子串。我們記錄下這個子串的長度;在枚舉結(jié)束后,我們找到的最長的子串的長度即為答案。
判斷重復字符
在上面的流程中,我們還需要使用一種數(shù)據(jù)結(jié)構(gòu)來判斷 是否有重復的字符,常用的數(shù)據(jù)結(jié)構(gòu)為哈希集合(即 C++ 中的 std::unordered_set,Java 中的 HashSet,Python 中的 set, JavaScript 中的 Set)。在左指針向右移動的時候,我們從哈希集合中移除一個字符,在右指針向右移動的時候,我們往哈希集合中添加一個字符。
至此,我們就完美解決了本題。
代碼
classSolution{
publicintlengthOfLongestSubstring(Strings) {
// 哈希集合,記錄每個字符是否出現(xiàn)過
Set<Character>occ=newHashSet<Character>();
intn=s.length();
// 右指針,初始值為 -1,相當于我們在字符串的左邊界的左側(cè),還沒有開始移動
intrk=-1,ans=0;
for(inti=0;i<n;++i) {
if(i!=0) {
// 左指針向右移動一格,移除一個字符
occ.remove(s.charAt(i-1));
? ? ? ? ?? }
while(rk+1<n&&!occ.contains(s.charAt(rk+1))) {
// 不斷地移動右指針
occ.add(s.charAt(rk+1));
++rk;
? ? ? ? ?? }
// 第 i 到 rk 個字符是一個極長的無重復字符子串
ans=Math.max(ans,rk-i+1);
? ? ?? }
returnans;
?? }
}
復雜度分析
時間復雜度:O(N)O(N),其中 NN 是字符串的長度。左指針和右指針分別會遍歷整個字符串一次。
空間復雜度:O(|\Sigma|)O(∣Σ∣),其中 \SigmaΣ 表示字符集(即字符串中可以出現(xiàn)的字符),|\Sigma|∣Σ∣ 表示字符集的大小。在本題中沒有明確說明字符集,因此可以默認為所有 ASCII 碼在 [0, 128)[0,128) 內(nèi)的字符,即 |\Sigma| = 128∣Σ∣=128。我們需要用到哈希集合來存儲出現(xiàn)過的字符,而字符最多有 |\Sigma|∣Σ∣ 個,因此空間復雜度為 O(|\Sigma|)O(∣Σ∣)。
網(wǎng)址