3. 無(wú)重復(fù)字符的最長(zhǎng)子串

題目

無(wú)重復(fù)字符的最長(zhǎng)子串

解題方案

思路 1: 暴力解法, 雙重循環(huán)遍歷

簡(jiǎn)單粗暴些, 找一個(gè)最長(zhǎng)子串, 那么我們用兩個(gè)循環(huán) ** 窮舉所有子串 **, 然后再用一個(gè)函數(shù)判斷該子串中有沒(méi)有重復(fù)的字符仇冯。

public int lengthOfLongestSubstring(String s) {
    int n = s.length();
    int ans = 0; // 保存當(dāng)前得到滿足條件的子串的最大值
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j <= n; j++) // 之所以 j<= n, 是因?yàn)槲覀冏哟?[i, j), 左閉右開(kāi)
            if (allUnique(s, i, j)) ans = Math.max(ans, j - i);  // 更新 ans
    return ans;
}

public boolean allUnique(String s, int start, int end) {
    Set<Character> set = new HashSet<>(); // 初始化 hash set
    for (int i = start; i < end; i++) { // 遍歷每個(gè)字符
        Character ch = s.charAt(i);
        if (set.contains(ch)) return false;  // 判斷字符在不在 set 中
        set.add(ch); // 不在的話將該字符添加到 set 里邊
    }
    return true;
}

時(shí)間復(fù)雜度:兩個(gè)循環(huán), 加上判斷子串滿足不滿足條件的函數(shù)中的循環(huán), O(n^3)

空間復(fù)雜度:使用了一個(gè) set, 判斷子串中有沒(méi)有重復(fù)的字符翰舌。由于 set 中沒(méi)有重復(fù)的字符, 所以最長(zhǎng)就是整個(gè)字符集, 假設(shè)字符集的大小為 m, 那么 set 最長(zhǎng)就是 m 坏挠。另一方面, 如果字符串的長(zhǎng)度小于 m, 是 n 奴愉。那么 set 最長(zhǎng)也就是 n 了冒晰。綜上, 空間復(fù)雜度為 O(min(m, n))廷没。

思路二:滑動(dòng)窗口

遺憾的是上邊的算法沒(méi)有通過(guò) leetCode, 時(shí)間復(fù)雜度太大, 造成了超時(shí)朝氓。我們?cè)趺磥?lái)優(yōu)化一下呢宪彩?

上邊的算法中, 我們假設(shè)當(dāng) i 取 0 的時(shí)候,

j 取 1, 判斷字符串 str [0, 1) 中有沒(méi)有重復(fù)的字符休讳。

j 取 2, 判斷字符串 str [0, 2) 中有沒(méi)有重復(fù)的字符。

j 取 3, 判斷字符串 str [0, 3) 中有沒(méi)有重復(fù)的字符毯焕。

j 取 4, 判斷字符串 str [0, 4) 中有沒(méi)有重復(fù)的字符衍腥。

做了很多重復(fù)的工作, 因?yàn)槿绻?str [0, 3) 中沒(méi)有重復(fù)的字符, 我們不需要再判斷整個(gè)字符串 str [0, 4) 中有沒(méi)有重復(fù)的字符, 而只需要判斷 str [3] 在不在 str [0, 3) 中, 不在的話, 就表明 str [0, 4) 中沒(méi)有重復(fù)的字符。

如果在的話, 那么 str [0, 5), str [0, 6), str [0, 7) 一定有重復(fù)的字符, 所以此時(shí)后邊的 j 也不需要繼續(xù)增加了纳猫。i++ 進(jìn)入下次的循環(huán)就可以了婆咸。

此外, 我們的 j 也不需要取 i + 1, 而只需要從當(dāng)前的 j 開(kāi)始就可以了。

綜上, 其實(shí)整個(gè)關(guān)于 j 的循環(huán)我們完全可以去掉了, 此時(shí)可以理解變成了一個(gè)「滑動(dòng)窗口」芜辕。


整體就是橘色窗口在依次向右移動(dòng)

判斷一個(gè)字符在不在字符串中, 我們需要可以遍歷整個(gè)字符串, 遍歷需要的時(shí)間復(fù)雜度就是 O(n), 加上最外層的 i 的循環(huán), 總體復(fù)雜度就是 O(n^2)尚骄。我們可以繼續(xù)優(yōu)化, 判斷字符在不在一個(gè)字符串, 我們可以將已有的字符串存到 Hash 里, 這樣的時(shí)間復(fù)雜度是 O(1), 總的時(shí)間復(fù)雜度就變成了 O(n)

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Set<Character> set = new HashSet<>();
        int ans = 0, i = 0, j = 0;
        while (i < n && j < n) {
            if (!set.contains(s.charAt(j))) {
                set.add(s.charAt(j++));
                ans = Math.max(ans, j - i);
            } else {
                set.remove(s.charAt(i++));
            }
        }
        return ans;
    }
}

時(shí)間復(fù)雜度:在最壞的情況下, while 循環(huán)中的語(yǔ)句會(huì)執(zhí)行 2n 次, 例如 abcdefgg, 開(kāi)始的時(shí)候 j 一直后移直到到達(dá)第二個(gè) g 的時(shí)候固定不變, 然后 i 開(kāi)始一直后移直到 n, 所以總共執(zhí)行了 2n 次, 時(shí)間復(fù)雜度為 O(n)侵续。

空間復(fù)雜度:和上邊的類似, 需要一個(gè) Hash 保存子串, 所以是 O(min(m, n))倔丈。

思路三:跳躍窗口

繼續(xù)優(yōu)化, 我們看上邊的算法的一種情況。


特殊情況

當(dāng) j 指向的 c 存在于前邊的子串 abcd 中, 此時(shí) i 向前移到 b, 此時(shí)子串中仍然含有 c, 還得繼續(xù)移動(dòng), 所以這里其實(shí)可以優(yōu)化状蜗。我們可以一步到位, 直接移動(dòng)到子串 c 的位置的下一位需五!

直接跳到重復(fù)字符的下一位

實(shí)現(xiàn)這樣的話, 我們將 set 改為 map, 將字符存為 key, 將對(duì)應(yīng)的下標(biāo)存到 value 里就實(shí)現(xiàn)了。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>();
        for (int j = 0, i = 0; j < n; j++) {
            if (map.containsKey(s.charAt(j))) {
                i = Math.max(map.get(s.charAt(j)), i);
            }
            ans = Math.max(ans, j - i + 1);
            map.put(s.charAt(j), j + 1); // 下標(biāo) + 1 代表 i 要移動(dòng)的下個(gè)位置
        }
        return ans;
    }
}

與解法二相比

由于采取了 i 跳躍的形式, 所以 map 之前存的字符沒(méi)有進(jìn)行 remove, 所以 if 語(yǔ)句中進(jìn)行了 Math.max(map.get(s.charAt(j)), i), 要確認(rèn)得到的下標(biāo)不是 i 前邊的轧坎。

還有個(gè)不同之處是 j 每次循環(huán)都進(jìn)行了自加 1, 因?yàn)?i 的跳躍已經(jīng)保證了 str [i, j] 內(nèi)沒(méi)有重復(fù)的字符串, 所以 j 直接可以加 1 宏邮。而解法二中, 要保持 j 的位置不變, 因?yàn)椴恢篮?j 重復(fù)的字符在哪個(gè)位置。

最后個(gè)不同之處是, ans 在每次循環(huán)中都進(jìn)行更新, 因?yàn)?ans 更新前 i 都進(jìn)行了更新, 已經(jīng)保證了當(dāng)前的子串符合條件, 所以可以更新 ans 。而解法二中, 只有當(dāng)當(dāng)前的子串不包含當(dāng)前的字符時(shí), 才進(jìn)行更新蜜氨。

時(shí)間復(fù)雜度:我們將 2n 優(yōu)化到了 n, 但最終還是和之前一樣, O(n)械筛。

空間復(fù)雜度:也是一樣的, O(min(m, n))

思路四:使用數(shù)組

和解法三思路一樣, 區(qū)別的地方在于, 我們不用 Hash, 而是直接用數(shù)組, 字符的 ASCII 碼值作為數(shù)組的下標(biāo), 數(shù)組存儲(chǔ)該字符所在字符串的位置飒炎。適用于字符集比較小的情況, 因?yàn)槲覀儠?huì)直接開(kāi)辟和字符集等大的數(shù)組埋哟。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128];
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1; // (下標(biāo) + 1) 代表 i 要移動(dòng)的下個(gè)位置
        }
        return ans;
    }
}

和解法 3 不同的地方在于, 沒(méi)有了 if 的判斷, 因?yàn)槿绻?index [s.charAt(j)] 不存在的話, 它的值會(huì)是 0, 對(duì)最終結(jié)果不會(huì)影響。

時(shí)間復(fù)雜度:O(n)郎汪。

空間復(fù)雜度:O(m), m 代表字符集的大小赤赊。這次不論原字符串多小, 都會(huì)利用這么大的空間。

總結(jié)

綜上, 我們一步一步的尋求可優(yōu)化的地方, 對(duì)算法進(jìn)行了優(yōu)化怒竿。又加深了 Hash 的應(yīng)用, 以及利用數(shù)組巧妙的實(shí)現(xiàn)了 Hash 的作用砍鸠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市耕驰,隨后出現(xiàn)的幾起案子爷辱,更是在濱河造成了極大的恐慌,老刑警劉巖朦肘,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭弓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡媒抠,警方通過(guò)查閱死者的電腦和手機(jī)弟断,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)趴生,“玉大人阀趴,你說(shuō)我怎么就攤上這事〔源遥” “怎么了刘急?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)浸踩。 經(jīng)常有香客問(wèn)我叔汁,道長(zhǎng),這世上最難降的妖魔是什么检碗? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任据块,我火速辦了婚禮,結(jié)果婚禮上折剃,老公的妹妹穿的比我還像新娘另假。我一直安慰自己,他們只是感情好怕犁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布边篮。 她就那樣靜靜地躺著开睡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苟耻。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天扶檐,我揣著相機(jī)與錄音凶杖,去河邊找鬼。 笑死款筑,一個(gè)胖子當(dāng)著我的面吹牛智蝠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奈梳,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼杈湾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了攘须?” 一聲冷哼從身側(cè)響起漆撞,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎于宙,沒(méi)想到半個(gè)月后浮驳,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捞魁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年至会,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谱俭。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奉件,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昆著,到底是詐尸還是另有隱情县貌,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布宣吱,位于F島的核電站窃这,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏征候。R本人自食惡果不足惜杭攻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疤坝。 院中可真熱鬧兆解,春花似錦、人聲如沸跑揉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至现拒,卻和暖如春辣垒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背印蔬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工勋桶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侥猬。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓例驹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親退唠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹃锈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容