leetcode —— 3.無重複字符的最長字串

題目

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

示例 1:
輸入: "abcabcbb"
輸出: 3 
解釋: 因爲無重複字符的最長子串是 "abc",所以其長度爲 3。

示例 2:
輸入: "bbbbb"
輸出: 1
解釋: 因爲無重複字符的最長子串是 "b",所以其長度爲 1。

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

 

方法1——暴力循環

        我們可以將字符串S所有可能的子串都找出來遍歷,遍歷的過程中判斷當前子是否有重複的字符。設一個int型的變量ans來存放最長子串2的長度,初始化爲0,將每一個無重複字符的子串長度與ans進行比較,並將較大的值賦給ans,直到循環結束,找出最長無重複字符的子串的長度。

        循環的話沒什麼好說的,雙重循環就可以找出所有的子串可能;而要判斷一個子串中是否有重複的字符,可以寫一個方法,使用HashSet來判斷是否有重複字符的存在,代碼如下:

 public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int ans = 0;
        int j=0;
        for (int i = 0; i < n; i++){
            for (j = i; j < n; j++){
                System.out.print(s.charAt(j));
                if (allUnique(s, i, j)){
                    ans = Math.max(ans, j - i + 1);
                }
            }
        }
        return ans;
    }

    public boolean allUnique(String s, int start, int end) {
        Set<Character> set = new HashSet<>();
        for(int i = start; i <= end; i++)
        {
            Character ch = s.charAt(i);
            if(set.contains(ch))
            {
                return false;
            }
            set.add(ch);
        }
        return true;
    }

 

方法2——滑動窗口

    使用一個while循環來完成判斷無重複字符的最長子串。每次向右擴展前,判斷右邊界的下一個字符是否已經存在於set集合中,如果不存在,則直接向右擴展(將該字符add到set集合中);若已經存在,則從左邊界開始remove字符直到窗口內無重複字符爲止。

 public int lengthOfLongestSubstring(String s) {
        Set<Character> set = new HashSet<>();
        
        int n = s.length();
        int i = 0, j = 0, ans = 0;
        
        while(i < n && j < n){
            Character ch = s.charAt(j);
            
            //如果該字符不存在於set集合中
            if(!set.contains(ch)) {
                //將該字符add到set中
                set.add(ch);
                //更新最長字串的長度
                ans = Math.max(ans, j-i+1);
                //繼續判斷下一個字符
                j++;
            }
            //如果該字符已經存在於set集合中
            else{
                //從左邊界開始刪除字符
                //直到窗口內不存在重複的字符
                set.remove(s.charAt(i));
                i++;
            }
        }

        return ans;
    }

 

方法3——優化的滑動窗口

其實可以用人的思維來思考這個問題。比如說有一個字符串"bcadace"(隨便寫的),如果使用方法2,當窗口滑動到 bcada 的時候,set中包含了a字符,這個時候應該將窗口左邊界向右移動,繼續判斷cada,直到da時方纔沒有重複字符的存在。但其實這樣做是沒有必要的,我們如果已經判斷出了a字符有重複,那麼直接從上一個a的下一個字符也就是d開始就可以了。而優化的滑動窗口就是這樣來進一步縮短代碼執行時間。

方法3將使用hashmap來存放字符以及字符所在的位置。之前使用set是因爲我們只需要判斷set中是否有重複的字符即可,並不需要知道字符所在的位置,然而這裏我們需要知道字符所在的位置,才能定位 i 的位置。

 public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> map = new HashMap<>();
        int n = s.length();
        int i = 0, j = 0, ans =0;
 
        for(j = 0; j < n; j++){
            //如果當前字符已經存在
            if(map.containsKey(s.charAt(j))){
                //如果字符在i,j區間的話,更新i爲字符所在位置的下一個
                //如果字符不在i,j區間的話,i不變
                i =  Math.max(i, map.get(s.charAt(j))+1);
            }
            //更新長度
            ans = Math.max(ans,j-i+1);
            //將j字符put到map中
            map.put(s.charAt(j),j);
        }
        return ans;
    }

圖解:以abba爲例

1.i=0; j=0,此時map中無數據,直接更新ans,此時map:('a', 0)

 

2. i=0; j=1,此時map中不存在字符b,直接更新ans,此時map:('a', 0)('b', 1)

3. i= 0; j = 2,此時map中已經存在了字符b

使用map.get('b')獲取到上一個出現的b所在的位置爲1,此時窗口將兩個b都包含在內,將 i 更新爲上一個b所在位置加1,i = 2,

此時map:('a', 0), ('b', 2)

4. i = 2,j = 3,雖然此時滑動窗口內並未包含重複字符(直接觀察),但是在程序中,會判斷出map中已經存在a,所以要更新 i 的值,比較後發現 i 的值爲2,要大於上一個a出現的位置加1 (等於1),所以說此時 i 的值仍然爲2, 計算出ans = 2

 

其實在這個算法中,我比較難理解的就是 i 值的更新。在我自己完成練習的時候,就認爲直接將 i 的值更新爲上一次出現的位置+1就可以了,然而這樣是不行的。如果是abba的話,最後一個a進行判斷如果 i 更新爲2,會得出錯誤的答案:3。其實 i 的更新是需要滿足上一個出現的重複字符存在於滑動窗口內(也就是說,上一個出現的重複字符的位置要大於i,如果不大於i,相當於當前滑動窗口並沒有重複字符,我們只需要跟新map就可以了)

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