Reorganize String

Medium Heaps

Problem

Given a string s, return any possible rearrangement of s such that no two adjacent characters are the same. If no such arrangement exists, return an empty string.

  • 1 ≤ s.length ≤ 500
  • s consists of lowercase English letters

Example

Input: s = "aab"
Output: "aba"

The brute-force approach would try all permutations of the string and check if any satisfy the condition of no two adjacent characters being the same, which is factorial in time complexity and infeasible for large inputs. The critical insight is to recognize that if any character appears more than half the length of the string (rounded up), it is impossible to rearrange the string to avoid adjacent duplicates. For example, in "aaab", 'a' appears 3 times out of 4 characters, which is more than (4 + 1) // 2 = 2, so no valid rearrangement exists. This feasibility check is a quick filter that prevents unnecessary computation.

Approach

Straightforward Solution

A brute-force approach would generate all permutations of s and check each for validity, which is O(n!) and impractical for even moderate string lengths.

Core Observation

The fundamental truth is that no character can occupy more than half of the positions (rounded up) in the string if the rearrangement is to avoid adjacent duplicates. This is because characters must be interleaved with others to separate identical characters.

Path to Optimal

The key recognition signals are 'no two adjacent characters are the same' and 'rearrangement of characters'. These indicate a greedy approach using a max-heap (priority queue) to always pick the character with the highest remaining frequency that is not the same as the previously placed character. This approach ensures that the most frequent characters are spaced out as much as possible.

Optimal Approach

Count the frequency of each character and verify feasibility. Use a max-heap to store characters by their negative frequency (to simulate max-heap in Python). At each step, pop the character with the highest frequency that is not the same as the previously used character, append it to the result, and then push back the previous character if it still has remaining frequency. This process continues until the heap is empty, guaranteeing a valid rearrangement if one exists.

Time

O(n log k)

Counting frequencies takes O(n). Building the heap takes O(k), where k is the number of unique characters. Each character is pushed and popped at most its frequency times, totaling O(n) heap operations, each O(log k). Since k ≤ 26 for lowercase letters, this is effectively O(n).

Space

O(k)

The frequency map and heap store up to k unique characters, where k ≤ 26 for lowercase English letters, so space is O(1) auxiliary with respect to input size.

Pattern Spotlight

Heap / Priority Queue (Greedy Frequency Scheduling)

Always pick the character with the highest remaining frequency that differs from the last placed character to maximize spacing and avoid adjacency conflicts.

Solution

1class Solution:
2 def reorganizeString(self, s: str) -> str:
3 freq = Counter(s)
4
5 if max(freq.values()) > (len(s) + 1) // 2:
6 return ""
7
8 heap = [(-count, char) for char, count in freq.items()]
9 heapq.heapify(heap)
10
11 prev = (0, "")
12 res = []
13
14 while heap:
15 count, char = heapq.heappop(heap)
16 res.append(char)
17
18 if prev[0] < 0:
19 heapq.heappush(heap, prev)
20
21 prev = (count + 1, char)
22
23 return "".join(res)

Step-by-Step Solution

1

Verify Rearrangement Feasibility by Frequency Analysis

3freq = Counter(s)
5if max(freq.values()) > (len(s) + 1) // 2:
6 return ""

Objective

To determine early if a valid rearrangement is impossible by checking the maximum character frequency.

Key Insight

If any character's count exceeds half the length of the string (rounded up), no rearrangement can prevent adjacent duplicates. This check quickly filters out impossible cases, saving computation time by returning an empty string immediately.

Interview Quick-Check

Core Logic

The feasibility condition max(freq.values()) <= (len(s) + 1) // 2 ensures that characters can be interleaved without adjacency.

Common Pitfalls & Bugs

Forgetting to perform this check can lead to infinite loops or incorrect results when no valid rearrangement exists.

2

Build and Initialize Max-Heap with Character Frequencies

8heap = [(-count, char) for char, count in freq.items()]
9heapq.heapify(heap)

Objective

To prepare a max-heap data structure that prioritizes characters by their remaining frequency for greedy selection.

Key Insight

Using a max-heap allows efficient retrieval of the character with the highest remaining count. Storing counts as negative values leverages Python's min-heap implementation to simulate a max-heap. This structure is essential for the greedy strategy to always pick the most frequent character available.

Interview Quick-Check

Core Logic

Heapifying the list of (-count, char) pairs enables O(log k) retrieval of the character with the highest frequency.

Complexity

Heap operations are efficient since the number of unique characters k is at most 26, making heap operations effectively constant time.

3

Greedily Construct Result by Alternating Characters Using Heap

11prev = (0, "")
12res = []
14while heap:
15 count, char = heapq.heappop(heap)
16 res.append(char)
18 if prev[0] < 0:
19 heapq.heappush(heap, prev)
21 prev = (count + 1, char)

Objective

To build the rearranged string by repeatedly selecting the most frequent character that differs from the previously used one.

Key Insight

By always choosing the character with the highest remaining frequency that is not the same as the last appended character, the algorithm spaces out identical characters to avoid adjacency. The previous character is stored and reinserted into the heap only after a different character is placed, ensuring no two identical characters are adjacent. This greedy approach guarantees a valid rearrangement if one exists.

Interview Quick-Check

Core Logic

The algorithm maintains a 'prev' variable to hold the last used character and its remaining count, pushing it back into the heap only after placing a different character to prevent immediate repetition.

State & Boundaries

The loop continues until the heap is empty, ensuring all characters are placed.

Common Pitfalls & Bugs

Failing to update 'prev' correctly or pushing it back too early can cause adjacent duplicates or infinite loops.

4

Return the Final Reorganized String

23return "".join(res)

Objective

To produce the final rearranged string by joining the accumulated characters.

Key Insight

Joining the list of characters into a string is the final step that converts the internal representation into the required output format. Since the algorithm guarantees no two adjacent characters are the same, the returned string satisfies the problem constraints.

Interview Quick-Check

Core Logic

Joining the list into a string returns the final rearranged string that meets the adjacency condition.

Line Analysis

This solution has 5 Critical lines interviewers watch for.

Line 5 Critical
if max(freq.values()) > (len(s) + 1) // 2:

Check if the maximum character frequency exceeds the allowed threshold.

This condition quickly identifies impossible cases where a character appears more than half the string length (rounded up), making rearrangement without adjacent duplicates impossible.

Line 18 Critical
if prev[0] < 0:

If the previous character still has remaining count, push it back into the heap.

Reinserting the previous character only after placing a different character ensures no two identical characters are adjacent and maintains correct frequency counts.

Line 6 Critical
return ""

Return an empty string if rearrangement is impossible.

Early termination prevents unnecessary computation and ensures correctness by signaling no valid solution exists.

Line 15 Critical
count, char = heapq.heappop(heap)

Pop the character with the highest remaining frequency from the heap.

Selecting the most frequent character available maximizes spacing between identical characters, reducing adjacency risk.

Line 21 Critical
prev = (count + 1, char)

Update the previous character to the current one with decremented count.

Decrementing the count reflects the usage of one occurrence of the character, and storing it as previous prevents immediate reuse, enforcing the adjacency rule.

Line 3 Core
freq = Counter(s)

Count the frequency of each character in the input string.

Frequency counting is essential to understand the distribution of characters and to detect if any character appears too frequently to allow a valid rearrangement.

Line 9 Core
heapq.heapify(heap)

Transform the list into a heap data structure.

Heapifying organizes the data to allow O(log k) insertion and extraction of the highest frequency character, which is critical for the greedy selection process.

Line 19 Core
heapq.heappush(heap, prev)

Push the previous character back into the heap for future selection.

This step preserves the availability of characters with remaining counts, enabling their eventual placement while avoiding immediate repetition.

Line 8 Core
heap = [(-count, char) for char, count in freq.items()]

Create a list of tuples with negative counts and characters for heap initialization.

Storing counts as negative values allows Python's min-heap to function as a max-heap, enabling efficient retrieval of the most frequent character.

Line 11 Core
prev = (0, "")

Initialize the previous character tracker with zero count and empty character.

This variable holds the character last appended to the result and its remaining count, enabling the algorithm to avoid placing the same character consecutively.

Line 16 Core
res.append(char)

Append the selected character to the result list.

Adding the chosen character to the result builds the output string incrementally while respecting the adjacency constraint.

Line 14 Standard
while heap:

Loop while there are characters left in the heap to process.

This loop drives the greedy construction of the rearranged string by repeatedly selecting the next character to append.

Line 23 Standard
return "".join(res)

Join the list of characters into a single string and return it.

Converting the accumulated characters into a string produces the final output in the required format.

Line 12 Standard
res = []

Initialize an empty list to accumulate the result characters.

Using a list for character accumulation is efficient for appending and later joining into the final string.

Test Your Understanding

Why must the maximum frequency of any character be at most (len(s) + 1) // 2 for a valid rearrangement to exist?

Because if a character appears more than half the length of the string (rounded up), it is impossible to separate all its occurrences with other characters, inevitably causing at least two identical characters to be adjacent.

Related Problems

Heaps pattern

Don't just read it. Drill it.

Reconstruct Reorganize String from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Reorganize String drill

or drill a free problem