1. 程式人生 > 實用技巧 >LeetCode: 3. 無重複字元的最長子串

LeetCode: 3. 無重複字元的最長子串

1. 核心思路

1. 滑動視窗

維持一個滑動的視窗,裡面的字元都是唯一的,如果滑動視窗的長度大於以前的最大子串,則更新最大長度

2. 為什麼要用滑動視窗

顯然,易證;由題意可知;略;懂的都懂,不懂的也解釋不了

我們不妨以示例一中的字串 abcabcbb 為例,找出 從每一個字元開始的,不包含重複字元的最長子串,那麼其中最長的那個字串即為答案。對於示例一中的字串,我們列舉出這些結果,其中括號中表示選中的字元以及最長的字串:

以(a)bcabcbb 開始的最長字串為 (abc)abcbb;
以a(b)cabcbb 開始的最長字串為 a(bca)bcbb;
以 ab(c)abcbb 開始的最長字串為 ab(cab)cbb;
以 abc(a)bcbb 開始的最長字串為 abc(abc)bb;
以 abca(b)cbb 開始的最長字串為 abca(bc)bb;
以 abcab(c)bb 開始的最長字串為 abcab(cb)b;
以 abcabc(b)b 開始的最長字串為 abcabc(b)b;
以 abcabcb(b) 開始的最長字串為 abcabcb(b)。

如果我們依次遞增地列舉子串的起始位置,那麼子串的結束位置也是遞增的!這裡的原因在於,假設我們選擇字串中的第 k個字元作為起始位置,並且得到了不包含重複字元的最長子串的結束位置為 r_k。那麼當我們選擇第 k+1個字元作為起始位置時,首先從 k+1到 r_k的字元顯然是不重複的,並且由於少了原本的第 k個字元,我們可以嘗試繼續增大 r_k,直到右側出現了重複字元為止。
(節選自:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetc-2/

)

2. 具體步驟

  • 由上述分析可知,我們要不斷列舉子串的起始位置,然後得出從哪個位置開始的無重複子串的長度最長
  • 顯然,並不是開始位置越小,無重複子串越長,只是開始位置越小可能的 無重複子串越長!
  • 結束位置呢?從字串第一個位置開始,不斷向後遍歷,直到有重複的字元為止
  • 用一個集合(set)儲存此次遍歷的字元,因為集合特性-無重複元素,集合裡的元素就是當前的無重複子串
  • 被結束位置指向的字元放入集合中
  • 結束的一次遍歷結束時,判斷一下是否超過以前的最大長度,超過了就更新最大長度
  • 如果出現了重複元素呢?怎麼進行下一次遍歷?如何定位到發生重複的元素?
  • 答案很簡單,在集合中刪除開始位置指向的字元,然後開始位置向前移動一步,開始下次遍歷。
  • 就這?九折?為什麼刪除開始位置指向的字串就完了啊?重複的元素又不一定是它,刪除後,下次遍歷一樣有重複元素啊
  • 確實。但那又有什麼影響呢?下次遍歷有重複,那麼開始位置繼續向前,直到沒有為止
  • 這就是一般人的思維和計算機思維的區別
  • 滑動視窗 的分析中可知結束位置不變的話,最大長度是不會變的,只要有重複元素,結束位置就不會變
  • 首先你要知道人是如何解決問題的,然後想到怎麼教計算機用類似的方法解決
  • 當然,大部分人想到的都是 暴力解法 ,暴力解法永遠是最後一個選項,如果你把自己當做一個合格的猿類

3. 實現程式碼

    public int lengthOfLongestSubstring(String s) {
        Set<Character> set = new HashSet<>();
        int n = s.length();
        int start = 0, end = -1, max =0;
        for(;start<n;start++){
            // end + 1表示下一個要新增的字元的下標
            // 初始值為-1,因為最開始要新增的字元下標為0,即下一個位置為0
            while (end+1 < n && !set.contains(s.charAt(end+1))){
                set.add(s.charAt(end +1));
                end ++;
            }
            // 遍歷完一個起始位置,多個結束位置後,判斷是否超過以前的最大值
            max = Math.max(max, end-start +1);
            // 以這個字元開頭的所有子串遍歷完了,刪除後遍歷下一個
            set.remove(s.charAt(start));
        }
        return max;
    }
def lengthOfLongestSubstring(self, s: str) -> int:
    c_set = set()
    start, end, ans, n = 0, -1, 0,len(s)
    for start in range(0, n):
        while end+1 < n and s[end+1] not in c_set:
            c_set.add(s[end+1])
            end += 1
        ans = max(ans, end-start+1)
        c_set.remove(s[start])

    return ans

人生苦短,我用Py

4. 最後總結

這是我的第一篇LeetCode題解,為什麼要寫這東西?加深自己印象,幫助他人解題

這題 的突破口是 要想到 從每一個字元開始的,不包含重複字元的最長子串,然後想到滑動視窗

計算機專業的對滑動視窗應該很熟悉了,那麼滑動視窗在計算機相關領域的哪些地方有實際運用呢?

沒經過任何訓練,沒得到任何提示就能想到這解法的確實厲害。