1. 程式人生 > 實用技巧 >每日一道 LeetCode (46):無重複字元的最長子串

每日一道 LeetCode (46):無重複字元的最長子串

每天 3 分鐘,走上演算法的逆襲之路。

前文合集

每日一道 LeetCode 前文合集

程式碼倉庫

GitHub: https://github.com/meteor1993/LeetCode

Gitee: https://gitee.com/inwsy/LeetCode

題目:無重複字元的最長子串

題目來源:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

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

示例 1:

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

示例 2:

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

示例 3:

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

解題方案一:暴力方案

看到這道題的第一個反應是這題我做過!!

然後我往前一頓翻,結果嘛。。。

emmmmmmmmmmm。。。

沒找著。

既然找不著那就開始想,這題是要求一個最長無重複的子串,重點總共就兩點:

  • 無重複
  • 子串

最後是要求一個最長的長度,這其實就是把所有的不重複子串的長度窮舉出來,然後取最大就完事兒。

經過上面的分析,我們成功的把這道題轉化成了一個求不重複子串的問題。

思路是滑動視窗,首先子串肯定是有長度的,即使只有 1 也是有長度的。

這時,我們指定兩個指標,一個左一個右,左右指標之間的內容實際上就是我們的子串,長度就是我們的子串長度。

兩個指標位於初始位置時,開始移動右指標,然後每次移動以後判斷指向的元素是否和之前已有的元素有重複,如果沒有重複就一直向右移,直到有重複的為止,這就是我們的第一個子串,然後記錄這個子串的長度。

接下來左指標右移一位,然後再重複上面的過程,一直到左指標移動完整個字串,這時我們就遍歷完成了所有的不重複的子串。

判斷子串是否用重複可以通過資料結構來判斷,通常常用的有雜湊表。

接下來就是程式碼實現:

public int lengthOfLongestSubstring(String s) {
    Set<Character> setChars = new HashSet<>();
    int length = s.length();
    // 定義右指標
    int right = -1;
    // 定義返回結果
    int result = 0;
    for (int i = 0; i < length; ++i) {
        if (i != 0) {
            // 左指標右移一次,刪掉前一個字元
            setChars.remove(s.charAt(i - 1));
        }
        while (right + 1 < length && !setChars.contains(s.charAt(right + 1))) {
            // 移動右指標,像 set 中新增字元
            setChars.add(s.charAt(right + 1));
            ++right;
        }
        result = Math.max(result, right - i + 1);
    }
    return result;
}

解題方案二:優化後

上面這個方案有個缺陷,就是每次左指標只是單純的 + 1 ,實際上左指標可以直接移動到右指標 + 1 的位置,因為當前的子串已經有重複了,直接跳過就好了。

public int lengthOfLongestSubstring_1(String s) {
    int length = s.length(), result = 0;
    Map<Character, Integer> map = new HashMap<>();
    for (int left = 0, right = 0; right < length; right++) {
        // 如果含有右指標指向的元素,則移動左指標
        if (map.containsKey(s.charAt(right))) {
            left = Math.max(map.get(s.charAt(right)), left);
        }
        result = Math.max(result, right - left + 1);
        map.put(s.charAt(right), right + 1);
    }
    return result;
}

解題方案三:極致優化

上面的方案還有沒有優化空間,當然有,我們對迴圈次數已經沒辦法優化了,那麼還能優化的就剩下了判斷當前字元是否存在。

比雜湊表定址還要快的可能有什麼?當然是直接運算元組咯~~~

首先可以定義一個 128 位的陣列,然後我們通過陣列進行判斷當前字元是否存在:

public int lengthOfLongestSubstring_2(String s) {
    int n = s.length();
    int result = 0;
    int[] charIndex = new int[128];
    for (int left = 0, right = 0; right < n; right++) {
        char c = s.charAt(right);
        left = Math.max(charIndex[c], left);
        result = Math.max(result, right - left + 1);
        charIndex[c] = right + 1;
    }
    return result;
}