Description

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

Example:

Input: s = “abcabcbb” Output: 3

Intuition#

用滑動視窗維護一個「無重複字元」的子字串,遇到重複時收縮左邊界。

  • 右邊界持續擴展,將字元加入視窗
  • 當發現重複字元時,移動左邊界直到視窗內沒有重複
  • 用 HashSet 或 HashMap 追蹤視窗內的字元

Approaches#

1: Brute Force#

  • 概念: 檢查所有子字串是否包含重複字元,找出最長的
  • 時間複雜度: O(n³)
  • 空間複雜度: O(min(n, m)),m 為字元集大小
Brute Force 程式碼
class Solution {
    fun lengthOfLongestSubstring(s: String): Int {
        var result = 0
        for (i in s.indices) {
            val seen = mutableSetOf<Char>()
            for (j in i until s.length) {
                if (s[j] in seen) break
                seen.add(s[j])
                result = maxOf(result, j - i + 1)
            }
        }
        return result
    }
}

⭐2: Sliding Window + HashMap#

  • 概念: 用 HashMap 記錄每個字元最後出現的位置,遇到重複時直接跳過左邊界
  • 時間複雜度: O(n)
  • 空間複雜度: O(min(n, m))
class Solution {
    fun lengthOfLongestSubstring(s: String): Int {
        val map = mutableMapOf<Char, Int>()
        var left = 0
        var result = 0
        for (right in s.indices) {
            if (s[right] in map) {
                left = maxOf(left, map[s[right]]!! + 1)
            }
            map[s[right]] = right
            result = maxOf(result, right - left + 1)
        }
        return result
    }
}

收縮左邊界時要用 maxOf(left, ...),因為重複字元的上次位置可能在目前左邊界之前(已經被移除的區域)。

🔑 Takeaways#

  • Pattern: 可變長度滑動視窗
  • 關鍵技巧: HashMap 記錄字元最後出現位置,可以讓左邊界直接跳到正確位置,避免逐步收縮