767. Reorganize String
MediumDescription
給定一個字串
s,重新排列字元使得相鄰兩個字元不同。若無法做到,回傳空字串。
Example:
Input: s = “aab” Output: “aba”
Intuition#
核心思路:每次貪心地取出現次數最多的字元放置,但要確保不與前一個字元相同。
- 若某個字元出現次數超過
(n + 1) / 2,則必定無解 - 每次取最多的字元放入結果,若與前一個相同,則改取第二多的
- Max Heap 天然支持每次取頻率最高的字元
Approaches#
1: Sorting by Frequency#
- 概念: 按頻率排序後交錯放置,先填偶數位再填奇數位
- 時間複雜度:
O(n) - 空間複雜度:
O(n)
class Solution {
fun reorganizeString(s: String): String {
val count = IntArray(26)
for (c in s) count[c - 'a']++
var maxCount = 0
var maxChar = 0
for (i in 0 until 26) {
if (count[i] > maxCount) {
maxCount = count[i]
maxChar = i
}
}
if (maxCount > (s.length + 1) / 2) return ""
val result = CharArray(s.length)
var idx = 0
// 先放最多的字元在偶數位
while (count[maxChar] > 0) {
result[idx] = ('a' + maxChar)
idx += 2
count[maxChar]--
}
// 再放剩下的
for (i in 0 until 26) {
while (count[i] > 0) {
if (idx >= s.length) idx = 1
result[idx] = ('a' + i)
idx += 2
count[i]--
}
}
return String(result)
}
}⭐2: Max Heap#
- 概念: 用 Max Heap 每次取頻率最高的兩個字元交替放置,確保相鄰不同
- 時間複雜度:
O(n log 26)=O(n) - 空間複雜度:
O(26)=O(1)
class Solution {
fun reorganizeString(s: String): String {
val count = IntArray(26)
for (c in s) count[c - 'a']++
val maxHeap = PriorityQueue<IntArray>(compareByDescending { it[1] })
for (i in 0 until 26) {
if (count[i] > 0) {
if (count[i] > (s.length + 1) / 2) return ""
maxHeap.offer(intArrayOf(i, count[i]))
}
}
val sb = StringBuilder()
while (maxHeap.size >= 2) {
val first = maxHeap.poll()
val second = maxHeap.poll()
sb.append(('a' + first[0]))
sb.append(('a' + second[0]))
first[1]--
second[1]--
if (first[1] > 0) maxHeap.offer(first)
if (second[1] > 0) maxHeap.offer(second)
}
if (maxHeap.isNotEmpty()) {
sb.append(('a' + maxHeap.poll()[0]))
}
return sb.toString()
}
}🔑 Takeaways#
- Pattern: 「重排避免相鄰重複」→ Max Heap 每次取最多的兩個字元交替放置
- 關鍵技巧: 判斷無解條件:最大頻率 > (n+1)/2;每次取兩個字元可以保證不衝突。此 pattern 也適用於 621. Task Scheduler 和 1405. Longest Happy String