【LeetCode滑動視窗專題#2】無重複字元的最長子串

2023-06-09 06:01:29

#1傳送門
滑動視窗最大值
長度最小的子陣列

無重複字元的最長子串

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

範例 1:

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

範例 2:

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

範例 3:

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

提示:

0 <= s.length <= 5 * 104
s 由英文字母、數位、符號和空格組成

思路

容易想到這裡需要使用滑動視窗的思想來解決問題

我們可以定義兩個指標,left和right

left一開始指向0,right則放入for迴圈中向前不斷遍歷字串s

這裡還需要維護一個雜湊表(選擇map,因為字元型別不止有26個字母)

使用當前遍歷值(字元)在表中查詢,如果沒出現過,就把當前字元放入雜湊表中

注意!!!!!!

這裡有可能我們會下意識的將雜湊表的鍵值結構定義為:"遍歷字元--字元出現次數"

在本題中,這是錯誤的,或者說是不合適的

因為我們需要找到重複字元第一次出現的位置,如以字元出現次數為值的話,無法實現這一目的(字元出現位置可以幫助我們確定哪個字元是第一個不重複的字元

舉個例子,假設有字串 "abccba",如果我們以字元出現次數為值來構建雜湊表,那麼雜湊表的值應該為 {'a': 2, 'b': 2, 'c': 2},這些字元的出現次數都是相同的。如果我們想要找到第一個不重複的字元,就需要進一步遍歷原字串,找到第一個出現次數為1的字元。但是,如果我們以字元出現位置為值來構建雜湊表,那麼雜湊表的值應該為 {'a': 0, 'b': 1, 'c': 2},我們可以在遍歷字串的過程中實時更新雜湊表,並找到第一個出現位置最小的字元。

因此,在這個問題中,以字元為鍵,以字元出現位置為值("遍歷字元--字元首次出現位置")是更加合適的選擇。

繼續

如果當前遍歷值在表中出現過,那麼我們在雜湊表中獲取該字元對應的值,即其在s中第一次出現的位置

將左指標移動到該位置(為什麼?),並且加1跳過該位置,目的是剔除重複值

這裡是本題的第二個坑

在寫的時候,第一版中我移動left時用了left++,但是這樣是錯的,不能簡單地使用left++來移動左指標,因為有可能之前的某個字元已經出現過,那麼我們需要更新左指標的位置,使得新的左指標位置可以捨棄之前出現過的字元,從而保證當前的子串不重複。

這其實還涉及到我們如何定義「重複出現」這件事情

因為在設計hash表時,儲存的是當前字元的索引,因此,我們僅僅在hash中查詢到某個字元還不夠,原因如下:

如果當前字元s[right]已經在雜湊表中存在,則說明該字元之前出現過,我們需要更新左指標的位置,使其跳過該重複字元,即左指標left的新位置應該是(hash[s[right]]+1)。但是,在更新左指標的同時,還需要確保新的左指標位置大於等於上一個子串的起始位置left,因為我們要尋找最長的無重複子串,而不僅僅是子串長度

因此,hash.find(s[right]) != hash.end() && hash[s[right]] >= left 確保了字元s[right]曾經出現過(也就是該字元在雜湊表中有對應的索引),並且其最後一次出現的下標大於等於當前子串的起始位置left,才會更新左指標的位置。

如果只寫 hash.find(s[right]) != hash.end() 的話,可能會把之前的那些已經被跳過的字元再次算進去,從而導致錯誤的結果。

例如:"abcabcbb"

如果當前左指標指向的是第一個b,右指標指向第二個a,說明我們已經判斷a重複出現,並把左指標移動到了hash[s[right]]+1

此時我們並沒有刪除hash表中關於a第一次出現的位置資訊,因此下一次如果還遇到a,我們不能讓左指標移動到第一次a的位置

所以需要加上限定條件,即hash[s[right]] >= left來保證左指標的正常跳轉

然後,更新當前字元在雜湊表中的位置值,即將當前字元位置設定為第一次出現位置

程式碼

關鍵點:雜湊表鍵值對設計(採用"遍歷字元--字元首次出現位置")、left指標的移動方式

步驟:

1、定義一個雜湊表,鍵為字元,值為字元出現的index

2、移動右指標遍歷字串s,在雜湊表中查詢當前遍歷字元

​ 如果有重複,同時判斷該重複值位置是否大於left指標位置(因為如果出現重複值,left指標所值的應該是上一次該值出現的位置),大於則將left移動到當前字元位置,並加1跳過當前位置

​ 如果無重複,先不處理

3、不管有無重複都對當前遍歷字元在雜湊表中的值(即位置索引)進行更新(同時也處理了無重複的情況)

4、左右指標作差與最大長度變數比較,並更新該變數

5、返回最大長度變數

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> hash;//建立hash表
        int left = 0;//左指標
        int maxLen = 0;
        
        for(int right = 0; right < s.size(); ++right){//遍歷字串s
            if(hash.find(s[right]) != hash.end() && left <= hash[s[right]]){//若當前字元在雜湊表中存在
                left = hash[s[right]] + 1;//獲取當前字元在雜湊表中對應的值,該值為字元在s中的索引,將左指標移動到此處
                //即若當前字元在雜湊表中存在,我們需要將left指標指到重複字元s[right]上次出現的位置,然後加1來跳過它
            }
            //對應情況:1、當前字元在雜湊表中不存在/2、跳轉left指標至重複值第一次出現位置之後,更新當前字元在雜湊表中的位置資訊
            hash[s[right]] = right;//新增字元到雜湊表/更新資訊
            if(right - left + 1 > maxLen) maxLen = right - left + 1;//更新當前最大長度
        }
        return maxLen;
    }
};