import heapq
from collections import Counter, deque
def rearrangeString(s: str, k: int) -> str:
if k == 0:
return s
freq = Counter(s)
maxHeap = [(-cnt, ch) for ch, cnt in freq.items()]
heapq.heapify(maxHeap)
queue = deque() # stores (cnt, ch, ready_time)
res = []
time = 0
while maxHeap or queue:
if maxHeap:
cnt, ch = heapq.heappop(maxHeap)
res.append(ch)
if -cnt > 1:
queue.append((cnt + 1, ch, time + k))
else:
# idle slot needed
if queue:
res.append('')
else:
break
time += 1
# if the front of the queue is ready to be pushed back into heap
if queue and queue[0][2] == time:
ready_cnt, ready_ch, _ = queue.popleft()
heapq.heappush(maxHeap, (ready_cnt, ready_ch))
result = ''.join(res)
return result if len(result) == len(s) else ""
#include <string>
#include <queue>
#include <unordered_map>
#include <vector>
using namespace std;
string rearrangeString(string s, int k) {
if (k == 0) return s;
unordered_map<char, int> freq;
for (char c : s) freq[c]++;
priority_queue<pair<int, char>> maxHeap;
for (auto& p : freq)
maxHeap.push({p.second, p.first});
queue<pair<int, char>> waitQueue;
string result;
while (!maxHeap.empty()) {
vector<pair<int, char>> temp;
int cnt = min(k, (int)s.size() - (int)result.size());
for (int i = 0; i < cnt; ++i) {
if (maxHeap.empty()) {
if (temp.empty()) return result;
return "";
}
auto top = maxHeap.top(); maxHeap.pop();
result += top.second;
if (--top.first > 0) temp.push_back(top);
}
for (auto& p : temp)
maxHeap.push(p);
}
return result.size() == s.size() ? result : "";
}
import java.util.*;
public class Solution {
public String rearrangeString(String s, int k) {
if (k == 0) return s;
Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray())
freq.put(c, freq.getOrDefault(c, 0) + 1);
PriorityQueue<Map.Entry<Character, Integer>> maxHeap =
new PriorityQueue<>((a, b) -> b.getValue() - a.getValue());
maxHeap.addAll(freq.entrySet());
Queue<Map.Entry<Character, Integer>> waitQueue = new LinkedList<>();
StringBuilder result = new StringBuilder();
while (!maxHeap.isEmpty()) {
List<Map.Entry<Character, Integer>> temp = new ArrayList<>();
int cnt = Math.min(k, s.length() - result.length());
for (int i = 0; i < cnt; i++) {
if (maxHeap.isEmpty()) {
if (temp.isEmpty()) return result.toString();
return "";
}
Map.Entry<Character, Integer> entry = maxHeap.poll();
result.append(entry.getKey());
if (entry.getValue() - 1 > 0) {
entry.setValue(entry.getValue() - 1);
temp.add(entry);
}
}
maxHeap.addAll(temp);
}
return result.length() == s.length() ? result.toString() : "";
}
}
function rearrangeString(s, k) {
if (k === 0) return s;
const freq = {};
for (const ch of s) {
freq[ch] = (freq[ch] || 0) + 1;
}
const maxHeap = [];
for (const ch in freq) {
maxHeap.push([freq[ch], ch]);
}
maxHeap.sort((a, b) => b[0] - a[0]);
const res = [];
while (maxHeap.length) {
const wait = [];
let cnt = Math.min(k, s.length - res.length);
for (let i = 0; i < cnt; ++i) {
if (!maxHeap.length) {
if (wait.length === 0) return res.join('');
return '';
}
let [count, ch] = maxHeap.shift();
res.push(ch);
if (--count > 0) wait.push([count, ch]);
}
for (const item of wait) maxHeap.push(item);
maxHeap.sort((a, b) => b[0] - a[0]);
}
return res.length === s.length ? res.join('') : '';
}
Given a string s
and an integer k
, rearrange the characters in s
so that the same characters are at least k
distance apart from each other. If it is not possible to rearrange the string to satisfy this condition, return an empty string ""
.
s
must be used exactly as many times as it appears in the input.s
if a valid rearrangement is possible.""
.
Example:
Input: s = "aabbcc"
, k = 3
Output: "abcabc"
(or any other string where same letters are at least 3 apart)
At first glance, this problem looks like a permutation or rearrangement task. A brute-force approach would be to try every possible permutation of the string and check if the k
-distance condition is met, but this is computationally infeasible for even moderate string lengths.
Instead, we need to organize the placement of characters so that the most frequent ones are spaced out as much as possible. A natural analogy is scheduling: the most "demanding" tasks (characters with the highest frequency) must be given enough "cooldown" time between appearances. If we always pick the most frequent character that is allowed (i.e., that is not in its cooldown), we can greedily build a valid string.
To do this efficiently, we need to:
Here is a step-by-step guide to the optimized solution:
s
.Why these choices?
Let's use the input s = "aabbcc"
, k = 3
.
a
, cooldown queue: [('a', 1, 3)]
ab
, cooldown queue: [('a', 1, 3), ('b', 1, 4)]
abc
, cooldown queue: [('a', 1, 3), ('b', 1, 4), ('c', 1, 5)]
abca
, cooldown queue: [('b', 1, 4), ('c', 1, 5)]
abcab
, cooldown queue: [('c', 1, 5)]
abcabc
, cooldown queue: []
All characters used, and each same character is at least 3 apart. Success!
Brute-force approach:
s
, and d the number of unique characters.In practice, both time and space are efficient and scalable for typical input sizes.
To rearrange a string so that no two identical characters are less than k
apart, we use a max-heap to always pick the most frequent available character, and a queue to manage cooldowns. This greedy approach ensures that the hardest-to-place characters are spaced out first, avoiding impossible arrangements later. The algorithm is efficient and elegant, leveraging priority queues and queues for optimal scheduling, and is a classic example of greedy + heap-based design for string rearrangement under constraints.