leetcode題解:無重復字符的最長子串

給定一個字符串,請你找出其中不含有重復字符的 最長子串 的長度。

示例 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)址

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetc-2/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容