Reorganize String
Difficulty: Medium Source: NeetCode
Problem
Given a string
s, rearrange the characters ofsso that any two adjacent characters are not the same. If this is not possible, return an empty string"".Example 1: Input:
s = "aab"Output:"aba"Example 2: Input:
s = "aaab"Output:""Constraints:
1 <= s.length <= 500sconsists of lowercase English letters
Prerequisites
Before attempting this problem, you should be comfortable with:
- Greedy algorithms — always placing the most frequent available character
- Max-heap — efficiently retrieving the character with the highest remaining count
- Frequency counting — using
Counterfrom thecollectionsmodule
1. Brute Force — Interleave Sorted Frequencies
Intuition
Sort characters by frequency descending and try to place them at even positions first, then odd positions. This is the classic “task scheduler” interleave trick. If the most frequent character appears more than (len + 1) // 2 times, it’s impossible.
Algorithm
- Count character frequencies.
- If any count exceeds
(len(s) + 1) // 2, return"". - Sort characters by frequency descending.
- Place them at even indices
0, 2, 4, ...then odd indices1, 3, 5, ....
Solution
from collections import Counter
def reorganizeString(s):
counts = Counter(s)
n = len(s)
if max(counts.values()) > (n + 1) // 2:
return ""
# Sort by frequency descending
chars = sorted(counts.keys(), key=lambda c: -counts[c])
result = [''] * n
idx = 0 # fill even positions first, then odd
for char in chars:
for _ in range(counts[char]):
if idx >= n:
idx = 1 # switch to odd positions
result[idx] = char
idx += 2
return ''.join(result)
print(reorganizeString("aab")) # "aba"
print(reorganizeString("aaab")) # ""
print(reorganizeString("vvvlo")) # "vlvov" or similar valid answer
Complexity
- Time:
O(n log n)— sort dominates - Space:
O(n)
2. Greedy Max-Heap
Intuition
Build a result character by character. At each step, greedily pick the character with the highest remaining count that is not the same as the last character placed. A max-heap makes retrieval of the best candidate O(log k) where k ≤ 26. If we can’t place any character (only the same one remains), it’s impossible.
The trick: after picking a character, hold it aside (don’t push back yet). Pick the next best character, place it, then push the held character back. This ensures we never place two of the same character in a row.
Algorithm
- Count frequencies. If any >
(n + 1) // 2, return"". - Build a max-heap
(-count, char). - While the heap is non-empty:
a. Pop
(neg_count1, char1)— the most frequent character. b. Appendchar1to result. c. If the heap is non-empty, pop(neg_count2, char2)— second most frequent. d. Appendchar2to result. e. Push back any characters that still have remaining count. - Return the result (check if its length equals
n— if not, return"").
Solution
import heapq
from collections import Counter
def reorganizeString(s):
counts = Counter(s)
n = len(s)
# Early impossibility check
if max(counts.values()) > (n + 1) // 2:
return ""
# Max-heap via negation: (-count, char)
heap = [(-cnt, ch) for ch, cnt in counts.items()]
heapq.heapify(heap)
result = []
while heap:
neg1, ch1 = heapq.heappop(heap)
result.append(ch1)
if heap:
neg2, ch2 = heapq.heappop(heap)
result.append(ch2)
if neg2 + 1 < 0: # still has remaining count
heapq.heappush(heap, (neg2 + 1, ch2))
if neg1 + 1 < 0: # push ch1 back after ch2 is placed
heapq.heappush(heap, (neg1 + 1, ch1))
return ''.join(result) if len(result) == n else ""
print(reorganizeString("aab")) # "aba"
print(reorganizeString("aaab")) # ""
print(reorganizeString("aabb")) # "abab" or "baba"
print(reorganizeString("vvvlo")) # valid arrangement like "vlvov"
Complexity
- Time:
O(n log k)where k ≤ 26 — n characters placed, each heap op is O(log k) - Space:
O(n)for the result
Common Pitfalls
Forgetting the impossibility check. If any character appears more than (len + 1) // 2 times, no valid arrangement exists. Always check this first — it saves time and avoids an infinite loop in naive implementations.
Pushing the current character back too early. In the pair-based approach, push ch1 back only after ch2 has been appended. If you push it back before placing ch2, the heap may immediately pop ch1 again and place it twice in a row.
Returning the result without length validation. In edge cases, the result might be shorter than n (e.g., only one character type remains and it’s the last). Check len(result) == n before returning.