Description
給定一個字串陣列
arr,從中選出子序列(可跳選)並依序串接,使得串接結果中所有字元唯一。回傳滿足條件的最長串接字串長度。
Example:
Input: arr = [“un”,“iq”,“ue”] Output: 4(“uniq” 或 “ique”)
Intuition#
核心思路:回溯法嘗試選或不選每個字串,用 bitmask 追蹤已使用的字元以快速判斷衝突。
- 每個字串選或不選 → 子集型回溯
- 需要快速判斷兩組字元是否有交集 → bitmask(26 位表示 a-z)
- 先過濾掉自身有重複字元的字串
Approaches#
1: 回溯#
- 概念: 對每個字串做選或不選的決策,用 Set 追蹤已選字元
- 時間複雜度:
O(2^n * m)- n 為陣列長度,m 為字串平均長度 - 空間複雜度:
O(n * m)
class Solution {
fun maxLength(arr: List<String>): Int {
var maxLen = 0
fun backtrack(idx: Int, current: Set<Char>) {
maxLen = maxOf(maxLen, current.size)
if (idx == arr.size) return
for (i in idx until arr.size) {
val s = arr[i]
// 檢查自身無重複且與 current 無交集
if (s.length == s.toSet().size && s.none { it in current }) {
backtrack(i + 1, current + s.toSet())
}
}
}
backtrack(0, emptySet())
return maxLen
}
}⭐2: Bitmask 回溯#
- 概念: 將每個字串轉為 bitmask,用位元 AND 快速判斷交集,回溯嘗試所有子集
- 時間複雜度:
O(2^n) - 空間複雜度:
O(n)
class Solution {
fun maxLength(arr: List<String>): Int {
// 預處理:將每個字串轉為 bitmask,過濾掉有重複字元的
val masks = mutableListOf<Pair<Int, Int>>() // (mask, length)
for (s in arr) {
var mask = 0
var valid = true
for (c in s) {
val bit = 1 shl (c - 'a')
if (mask and bit != 0) {
valid = false
break
}
mask = mask or bit
}
if (valid) masks.add(mask to s.length)
}
var maxLen = 0
fun backtrack(idx: Int, usedMask: Int, length: Int) {
maxLen = maxOf(maxLen, length)
for (i in idx until masks.size) {
val (mask, len) = masks[i]
// 位元 AND 檢查無交集
if (usedMask and mask == 0) {
backtrack(i + 1, usedMask or mask, length + len)
}
}
}
backtrack(0, 0, 0)
return maxLen
}
}🔑 Takeaways#
- Pattern: 子集型回溯 + bitmask 加速字元衝突判斷
- 關鍵技巧: 26 個字母用 Int 的 26 位表示,
AND == 0表示無交集,OR合併已用字元;預先過濾自身有重複的字串減少搜索空間