算法修煉之路—【字符串】Leetcode 3 無重複字符的最長子串

題目描述

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

示例1:

輸入: s = “abcabcbb”
輸出: 3
解釋: 因爲無重複字串的最長子串是“abc”,所以其長度爲 3.

示例2:

輸入: s = “bbbbb”
輸出: 1
解釋: 因爲無重複字符的最長子串是“b”,所以其長度爲1.

示例3:

輸入: s = “pwwkew”
輸出: 3
解釋: 因爲無重複字符的最長子串是“wke”,所以其長度爲3.

說明:
請注意,你的答案必須是子串的長度,示例3中,"pwke"使用一個子序列,而不是子串

思路分析

我們通過題意可以理解是尋找無重複字符的最長子串,返回其長度。這裏我們講問題進行拆解:

  1. 無重複字符
  2. 最長子串

對於問題1 無重複字符,需要無重複字符,這裏我們爲尋求較好的時間複雜度不採取對每一個字符c在某範圍內都進行搜索判重的方法。這裏我們藉助輔助容器來達到記錄歷史的目的,進而當遍歷到後面的字符時,直接在輔助容器中查詢是否包含即可。
這一題讓我直接聯想到得是計算機網絡中TCP的滑動窗口,我們通過界定窗口的左邊界和右邊界來判斷需要的數據區域,這裏即爲包括left不包括right的字符串均無重複字符。這裏我們通過圖示來說明:
在這裏插入圖片描述
圖1

我們通過兩個下標來確定滑動窗口的範圍left, right,且爲左閉右開;我們藉助集合set來存儲遍歷歷史,可知我們每個元素均爲唯一。

  1. s.charAt(right)沒有在set中時,先添加進set,且right++,即滑動窗口右擴展,重複步驟1
  2. set已經包括了s.charAt(right),則此時一定是left下標的字符爲重複字符,滑動窗口左收縮,且更新leftleft++

接下來再看問題2 最長子串,我們這裏需要返回的是這個最長子串的長度,即一個整型數據,我們這裏可採用直接對比的方式,每次都更新爲比較值得最大值即可:
maxSubStrLen = Math.max(maxSubStrLen , currLen)
此時可以得知解題步驟爲:

核心操作

  1. s.charAt(right)沒有在set中時,先添加進set,且right++,即滑動窗口右擴展,重複步驟1更新maxSubStrLen
  2. set已經包括了s.charAt(right),則此時一定是left下標的字符爲重複字符,滑動窗口左收縮,且更新leftleft++

解題代碼1

    public static int solution1(String s){
        if(s == null) return 0;
        
        int len = s.length();
        if(len == 1) return 1;
        
        /* Step 1: Init. integer and container.
        */
        int maxSubStrLen = 0, left = 0, right = 0;        
        Set<Character> set = new HashSet<>();
        
        /* Step 2: go-through string
        and
        check whether repeated in container to re-fine sliding window
        */
        while(left < len && right < len){
            
            if(!set.contains(s.charAt(right))){ // enlarge window on the right
                set.add(s.charAt(right++));
                maxSubStrLen = Math.max(maxSubStrLen, right-left);
            }else{ // shrink window from left
                set.remove(s.charAt(left++));
            }
        }
        
        
        return maxSubStrLen;
    }

複雜度分析

時間複雜度: 最壞情況下,需要對每個字符都訪問兩邊,2N,故時間複雜度爲O(N);
空間複雜度: 我們藉助了輔助容器,且考慮字符集大小M的情況下,空間複雜度爲O(min(M, N)).

進階思考

solution1 中最壞情況(均爲同一個字符),我們對每個字符都需要訪問兩遍,能否存在一種方法使得訪問字符的次數減少。我們已知字符的可取範圍是有限的,這裏假定爲M,則不妨在right界定右邊界的情況下,判斷並更新滑動窗口內的有且唯一的字符的下標,更新maxSubStrLen
在這裏我們依然需要藉助輔助容器,且還需要記錄字符的下標,則可選取輔助容器爲哈希表HashMap<char, index>, key爲瀏覽過的字符,value爲滑動窗口內(此時包含right在內)的相應字符的最新下標
則可總結爲下:

核心操作

  1. map中包含s.charAt(right)時,更新重複字符left的最新下標;否則直接進入步驟2
  2. 更新maxSubStrLen,同時將s.charAt(right)添加入map;

解題代碼2

    public static int solution2(String s){
        if(s == null) return 0;
        
        int len = s.length();
        if(len == 1) return 1;
        
        /* Step 1: Init. integer and container.
        */
        int maxSubStrLen = 0;        
        // <char, index>
        Map<Character, Integer> map = new HashMap<>();
        
        /* Step 2: go-through string
        and
        update existed index if checked.
        */
        for(int left=0, right=0; right < len; right++){
            if(map.containsKey(s.charAt(right))){
               left = Math.max(map.get(s.charAt(right)), left);
            }
            maxSubStrLen = Math.max(maxSubStrLen, right - left + 1);
            map.put(s.charAt(right), right + 1);
        }
        
        
        return maxSubStrLen;
    }

複雜度分析

時間複雜度: 這裏我們對數據進行了一次遍歷,故時間複雜度爲O(N);
空間複雜度: 我們藉助了輔助容器,同solution1一樣,爲O(min(M, N))

Github源碼

完整可運行文件請訪問GitHub

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章