The Reorganize String problem is a common interview question that tests how well you handle greedy construction under strict constraints. Many candidates start by shuffling characters randomly, only to realize that duplicates can easily force identical letters to end up adjacent. This problem pushes you to think in terms of character frequencies and always choosing the best next option to keep the arrangement valid. It’s a great exercise in using a priority queue to build a string while avoiding impossible states.
Problem statement
You are given a lowercase string s, and you need to rearrange its characters so that no two neighboring characters are the same. If such a rearrangement exists, return any valid one; otherwise, return an empty string.
Constraints:
- 1 <=
s.length<= 500 sconsists of lowercase English letters
Examples
| Input | Output | Explanation |
| “xxy” | “xyx” | One valid rearrangement is “xyx”, where adjacent characters differ. |
| “xxxy” | “” | No rearrangement can avoid adjacent duplicates because ‘x’ appears too many times. |
| “wwwbbc | “wbwbcw” | One valid rearrangement is “wbwbcw”, where adjacent characters differ. |
Naive approach: Trying all permutations
A straightforward idea is to generate all permutations of the string (or keep shuffling) until you find one where no two adjacent characters are the same. The problem is that permutations grow factorially, so even a small increase in n makes the search blow up. And even with “smart” backtracking, you often end up exploring many nearly identical partial strings, repeatedly failing for the same reason, usually because one high-frequency character can’t be placed without causing collisions.
Optimized approach using Max-Heap Greedy insight
A better way is to treat each character like a “task” that must be scheduled a certain number of times, with the constraint that the same task cannot run twice in a row. The hardest character to deal with is always the one that appears the most, so we want to place the currently most frequent character whenever possible.
To do this efficiently, we store characters in a max-heap keyed by remaining frequency. Each step, we pick the character with the highest remaining count, append it to the result, and decrease its count. To prevent duplicates, we keep the previously used character out of the heap for one step (if it still has remaining occurrences). After placing a different character, we safely push the previous one back into the heap.
If we ever reach a point where the heap is empty but we still have a “held-back” character with remaining count, then there’s no valid next move, meaning no rearrangement can satisfy the rule, so we return an empty string.
Solution steps
- Create a frequency map
freqfor all characters ins. - Create a heap where each entry is
(-count, ch)so the character with the highest remaining count comes out first. - Create an empty list
resultto build the answer. - Initialize
prev_char = Noneandprev_count = 0to track the last used character that may need to be pushed back. - While the heap is not empty:
- Pop the character with the highest remaining count.
- Append it to the result.
- Since you used one instance, increase its (negative) count by
+1(moving it toward zero). - If
prev_charstill has remaining occurrences (prev_count < 0), push it back into the heap. - Set
prev_charandprev_countto the character you just used and its updated count.
- If you successfully build a string of length
len(s), return it; otherwise return"".
To better understand the algorithm, let’s walk through an illustration example below.
Python code implementation
Let’s look at the code for this solution below:
Time complexity
The time complexity is O(n log a) where n is the length of the string and a is the number of distinct characters. Each character placement involves heap operations, and the heap size is at most the number of unique letters (up to 26 here), so the log factor is small but still present.
Space complexity
The space complexity is O(a) for the frequency map and heap, plus O(n) for the output string being built. In the worst case (all characters distinct), the heap and counter both track all distinct characters.
Edge cases
- Single character string
- Example:
"a" - Why it works: the result is
"a", and there are no adjacent pairs to violate the rule.
- Example:
- All characters the same
- Example:
"aaaa" - Why it works: the algorithm will place one
'a', then the heap becomes empty while'a'still remains held asprev, so the final length check fails and returns"".
- Example:
- One character dominates but still possible
- Example:
"aaabc" - Why it works: the heap keeps selecting
'a'often, but always forces an alternate character between consecutive'a'placements when available.
- Example:
- Many distinct characters
- Example:
"abcde" - Why it works: every placement is safe, and the heap simply returns characters until exhausted.
- Example:
Common pitfalls
- Not preventing immediate reuse of the last character
- Mistake: pop the max character repeatedly without a “cooldown,” which can create adjacent duplicates.
- Forgetting to push the previously used character back
- Mistake: holding
prevbut never re-inserting it causes missing characters and incorrect results.
- Mistake: holding
- Returning the built string without validating length
- Mistake: in impossible cases, you can end early; the length check is what correctly turns that into
"".
- Mistake: in impossible cases, you can end early; the length check is what correctly turns that into
- Overcomplicating with manual “fill even indices then odd indices” without careful checks
- Mistake: can work, but off-by-one errors and incorrect feasibility handling are common in interviews.