題解 | 「力扣」第 3 題:無重復字符的最長子串(中等,滑動窗口)

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

示例 1

輸入: s = "abcabcbb"
輸出: 3 
解釋: 因為無重復字符的最長子串是 "abc",所以其長度為 3。

示例 2:

輸入: s = "bbbbb"
輸出: 1
解釋: 因為無重復字符的最長子串是 "b",所以其長度為 1。

示例 3:

輸入: s = "pwwkew"
輸出: 3
解釋: 因為無重復字符的最長子串是 "wke",所以其長度為 3。
     請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。

示例 4:

輸入: s = ""
輸出: 0

數(shù)據(jù)范圍

  • 0 <= s.length <= 5 * 10^4
  • s 由英文字母、數(shù)字、符號和空格組成

暴力解法

這道問題的暴力解法是:

  • 枚舉所有的子串,O(N^2);
  • 再對每一個子串,再對每一個子串判斷是否有重復字符 O(N);

因此總的時間復雜度為 O(N^3)。

暴力解法的優(yōu)化

注意到這樣的事實:

  • 如果一個子串包含重復字符,那么與它有相同左端點的、長度更長的字符串一定也包含重復字符;
  • 又由于題目只要求我們找最長不重復子串的 長度(只要求返回長度,不要求得到具體的子串的結(jié)果),如果已經(jīng)找到了一個長度為 n 的子串,那么小于等于長度 n 的子串就沒有必要再枚舉了。

基于這兩點,我們就可以使用 右指針左指針交替向右移動的方式 考慮完所有暴力解法需要考慮的子串,也就是說「滑動窗口」的解法實現(xiàn)了「剪枝」的效果。

這里請大家在腦海里想象一下,實在在腦海里想不太明白的話,在草稿紙上做一些簡單的計算就非常清楚了。

暴力解法我們需要考慮所有可能的子串,我們把它們列出表格如下。我么用數(shù)對 (i,j) 表示子串 s[i..j] 。

而使用滑動窗口算法我們需要考慮的子串如下圖所示。

可以看出,滑動窗口算法只要右指針移動到數(shù)組的末尾程序就結(jié)束了。暴力解法需要考慮的子串的數(shù)量級是 O(N^2)(具體我們就不計算它了),而滑動窗口我們需要考慮的子串為 O(2N) = O(N)(我沒有寫得很具體,相信大家能明白我的意思)。

還需要解決的一個問題,如何判斷子串里是否有重復的字符,很容易想到使用「哈希表」統(tǒng)計字符出現(xiàn)次數(shù)。注意到題目的「數(shù)據(jù)范圍」里說到:s 由英文字母、數(shù)字、符號和空格組成。我們還可以使用數(shù)組代替哈希表,事實上哈希表的底層也是數(shù)組。

  • 當右指針向右移動將字符納入滑動窗口的時候,字符的頻數(shù)加 1;
  • 當左指針向右移動將字符移出滑動窗口的時候,字符的頻數(shù)減 1。

其它細節(jié)我們就不贅述了。請見「參考代碼」:

public class Solution {

    public int lengthOfLongestSubstring(String s) {
        int len = s.length();
        if (len < 2) {
            return len;
        }

        // 當 window 中某個字符的頻數(shù)為 2 時,表示滑動窗口內(nèi)有重復字符
        // 頻數(shù)數(shù)組,128 由測試數(shù)據(jù)的范圍決定
        int[] freq = new int[128];
        // 轉(zhuǎn)換為字符數(shù)組,避免每一次 s.charAt() 方法檢查下標越界
        char[] charArray = s.toCharArray();
        int left = 0;
        int right = 0;
        int res = 1;
        // 循環(huán)不變量:區(qū)間[left..right] 內(nèi)沒有重復元素
        while (right < len) {
            freq[charArray[right]]++;
            // 此時 [left..right) 內(nèi)如果沒有重復元素,就嘗試擴張 right
            // 否則縮小左邊界,while 循環(huán)體內(nèi)不斷縮小邊界
            if (freq[charArray[right]] == 2) {
                while (freq[charArray[right]] == 2) {
                    freq[charArray[left]]--;
                    left++;
                }
            }

            // 此時 [left..right] 內(nèi)沒有重復元素
            res = Math.max(res, right - left + 1);
            right++;
        }
        return res;
    }
}

時間復雜度:右指針遍歷了數(shù)組一次、左指針還沒有遍歷到數(shù)組的末尾就停了下來,因此時間復雜度為 O(N)。

本題小結(jié)

滑動窗口的問題基本上都可以寫成「雙重循環(huán)」的樣子,其中:

  • 外層循環(huán)讓右指針向右移動;
  • 內(nèi)層循環(huán)讓左指針向右移動。

在「力扣」的題解區(qū)有很多大佬提供了代碼的模板,但是我們在這里建議大家通過練習去理解每一行代碼的意思,不同的問題具體細節(jié)不一樣,不可以生搬硬套。


歡迎大家關(guān)注我的公眾號「算法不好玩」,B 站搜索「liweiwei1419」,我講解的算法知識特別好懂。

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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