Description
給定字串
s,將其分割成最少的子字串,使每個子字串中的字元都不重複。回傳最少的分割數量。
Example:
Input: s = “abacaba” Output: 4 (“ab”, “ac”, “ab”, “a”)
Intuition#
核心思路:Greedy——盡可能讓每個子字串最長(包含最多不重複字元)。遇到重複字元時就開始新的子字串。
- 使用 HashSet 追蹤當前子字串的字元
- 遇到已存在的字元 -> 計數+1,清空 Set,開始新子字串
- Greedy 正確性:讓每段盡量長不會讓後面的分割更差
Approaches#
1: HashSet Greedy#
- 概念: 用 HashSet 記錄當前子字串的字元,遇到重複就清空並計數
- 時間複雜度:
O(n)- 一次遍歷 - 空間複雜度:
O(26)=O(1)- 最多 26 個字母
class Solution {
fun partitionString(s: String): Int {
val seen = HashSet<Char>()
var count = 1
for (c in s) {
if (c in seen) {
count++
seen.clear()
}
seen.add(c)
}
return count
}
}⭐2: Bitmask Greedy#
- 概念: 用 26-bit 整數取代 HashSet,每個 bit 代表一個字母是否出現過
- 時間複雜度:
O(n)- 一次遍歷 - 空間複雜度:
O(1)- 一個整數
class Solution {
fun partitionString(s: String): Int {
var mask = 0
var count = 1
for (c in s) {
val bit = 1 shl (c - 'a')
if (mask and bit != 0) {
count++
mask = 0
}
mask = mask or bit
}
return count
}
}初始化
count = 1而非 0,因為即使沒有重複,字串本身就是一個子字串。最後一段子字串不會觸發count++,所以初始值要從 1 開始。
🔑 Takeaways#
- Pattern: 最少分割使每段無重複 -> Greedy 最長延伸
- 關鍵技巧: Bitmask 取代 HashSet 在小字母表場景下更高效;Greedy 的正確性來自於「當前段越長,後面的壓力不會更大」