767. Reorganize String
Problem Description
You are given a string s
consisting of characters. Your task is to rearrange the characters in the string such that no two adjacent characters are the same.
The goal is to find any valid rearrangement where consecutive characters are different. If it's impossible to create such an arrangement, return an empty string ""
.
For example:
- If
s = "aab"
, you could rearrange it to"aba"
where no adjacent characters are identical - If
s = "aaab"
, no valid rearrangement exists since you have too many 'a's to separate them all, so you would return""
The key challenge is determining when a rearrangement is possible and then constructing a valid arrangement when it exists. A rearrangement becomes impossible when any single character appears too frequently to be separated by other characters.
Intuition
To avoid having adjacent characters be the same, we need to spread out the characters as much as possible. Think of it like placing items on a shelf where identical items can't be next to each other.
The first key insight is determining when it's impossible to create a valid arrangement. If any character appears more than (n + 1) // 2
times (where n
is the string length), it's impossible to separate all occurrences. For example, in a string of length 5, if a character appears 4 times, we can't arrange them without having at least two adjacent occurrences.
The second insight is about the placement strategy. To maximize separation between identical characters, we can use an alternating pattern - place characters at positions 0, 2, 4, 6... (even indices) first, then fill positions 1, 3, 5, 7... (odd indices). This creates maximum spacing between identical characters.
Why start with the most frequent characters? By placing the most frequent characters first using this alternating pattern, we ensure they get the maximum possible separation. If we placed less frequent characters first, we might run out of "good" positions for the more frequent ones.
The alternating placement works because:
- When we place a character at even indices (0, 2, 4...), all occurrences are separated by at least one position
- Once we run out of even indices, we switch to odd indices (1, 3, 5...)
- This ensures that characters placed at even positions never touch characters at odd positions of the same type
This greedy approach of handling the most frequent characters first with alternating placement guarantees that if a valid arrangement exists, we'll find it.
Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation follows the intuition of alternating placement with a greedy approach:
-
Count character frequencies: Use a
Counter
to count occurrences of each character in the string. This gives uscnt
wherecnt[char]
is the frequency of that character. -
Check validity: Find the maximum frequency among all characters with
mx = max(cnt.values())
. Ifmx > (n + 1) // 2
, return an empty string since it's impossible to arrange the characters without adjacency. -
Initialize the result array: Create an array
ans
of sizen
to build our rearranged string. Using an array allows O(1) access to any position. -
Sort by frequency: Use
cnt.most_common()
to get characters sorted by their frequency in descending order. This ensures we place the most frequent characters first. -
Alternating placement algorithm:
- Start with index
i = 0
(first even position) - For each character
k
with frequencyv
:- Place the character at position
i
- Decrement the frequency:
v -= 1
- Move to the next even position:
i += 2
- When we exceed the array bounds (
i >= n
), switch to odd positions by settingi = 1
- Place the character at position
This creates the pattern: positions 0, 2, 4, 6... then 1, 3, 5, 7...
- Start with index
-
Build the result: After filling all positions, join the array into a string with
''.join(ans)
.
The time complexity is O(n log k) where k is the number of unique characters (for sorting), and space complexity is O(n) for the result array. The alternating index pattern guarantees that identical characters are maximally separated, preventing any adjacent duplicates if a valid arrangement exists.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "aaabbc"
:
Step 1: Count character frequencies
- 'a': 3 occurrences
- 'b': 2 occurrences
- 'c': 1 occurrence
- String length n = 6
Step 2: Check validity
- Maximum frequency = 3 (from 'a')
- Check: 3 ≤ (6 + 1) // 2 = 3 ✓
- Valid arrangement is possible!
Step 3: Initialize result array
ans = [_, _, _, _, _, _]
(6 empty positions)
Step 4: Sort characters by frequency (descending)
- Order: 'a'(3), 'b'(2), 'c'(1)
Step 5: Place characters using alternating pattern
Start with i = 0
(even positions first):
-
Place 'a' (3 times):
- Place 'a' at position 0:
ans = [a, _, _, _, _, _]
, i = 2 - Place 'a' at position 2:
ans = [a, _, a, _, _, _]
, i = 4 - Place 'a' at position 4:
ans = [a, _, a, _, a, _]
, i = 6 - i = 6 ≥ n, so switch to odd positions: i = 1
- Place 'a' at position 0:
-
Place 'b' (2 times):
- Place 'b' at position 1:
ans = [a, b, a, _, a, _]
, i = 3 - Place 'b' at position 3:
ans = [a, b, a, b, a, _]
, i = 5
- Place 'b' at position 1:
-
Place 'c' (1 time):
- Place 'c' at position 5:
ans = [a, b, a, b, a, c]
, i = 7
- Place 'c' at position 5:
Step 6: Build final result
- Join array:
"ababac"
The final string "ababac" has no adjacent identical characters! The alternating placement pattern ensured that the most frequent character 'a' was maximally spread out at positions 0, 2, and 4, while other characters filled the gaps.
Solution Implementation
1from collections import Counter
2from typing import Optional
3
4class Solution:
5 def reorganizeString(self, s: str) -> str:
6 # Get the length of the input string
7 string_length = len(s)
8
9 # Count frequency of each character
10 char_frequency = Counter(s)
11
12 # Find the maximum frequency among all characters
13 max_frequency = max(char_frequency.values())
14
15 # Check if reorganization is possible
16 # If any character appears more than (n+1)/2 times, it's impossible
17 # to arrange without having two adjacent same characters
18 if max_frequency > (string_length + 1) // 2:
19 return ''
20
21 # Initialize result array with None values
22 result = [None] * string_length
23
24 # Start placing characters at even indices (0, 2, 4, ...)
25 current_index = 0
26
27 # Process characters in descending order of frequency
28 # This ensures the most frequent character gets spread out first
29 for character, frequency in char_frequency.most_common():
30 # Place all occurrences of the current character
31 while frequency > 0:
32 # Place character at current position
33 result[current_index] = character
34 frequency -= 1
35
36 # Move to next even index
37 current_index += 2
38
39 # If we've exhausted even indices, switch to odd indices (1, 3, 5, ...)
40 if current_index >= string_length:
41 current_index = 1
42
43 # Convert the result array to a string and return
44 return ''.join(result)
45
1class Solution {
2 public String reorganizeString(String s) {
3 // Count frequency of each character
4 int[] charFrequency = new int[26];
5 int maxFrequency = 0;
6
7 // Count occurrences and find maximum frequency
8 for (char c : s.toCharArray()) {
9 int index = c - 'a';
10 charFrequency[index]++;
11 maxFrequency = Math.max(maxFrequency, charFrequency[index]);
12 }
13
14 // Check if reorganization is possible
15 // If any character appears more than (n+1)/2 times, it's impossible
16 int stringLength = s.length();
17 if (maxFrequency > (stringLength + 1) / 2) {
18 return "";
19 }
20
21 // Count number of unique characters
22 int uniqueCharCount = 0;
23 for (int frequency : charFrequency) {
24 if (frequency > 0) {
25 uniqueCharCount++;
26 }
27 }
28
29 // Create array to store character frequencies and their indices
30 int[][] charData = new int[uniqueCharCount][2];
31 int dataIndex = 0;
32 for (int i = 0; i < 26; i++) {
33 if (charFrequency[i] > 0) {
34 charData[dataIndex][0] = charFrequency[i]; // frequency
35 charData[dataIndex][1] = i; // character index
36 dataIndex++;
37 }
38 }
39
40 // Sort characters by frequency in descending order
41 // This ensures most frequent characters are placed first
42 Arrays.sort(charData, (a, b) -> b[0] - a[0]);
43
44 // Build result string by placing characters at alternating positions
45 StringBuilder result = new StringBuilder(s);
46 int position = 0;
47
48 // Place each character at alternating positions (0, 2, 4, ... then 1, 3, 5, ...)
49 for (int[] charInfo : charData) {
50 int frequency = charInfo[0];
51 int charIndex = charInfo[1];
52
53 while (frequency > 0) {
54 result.setCharAt(position, (char) ('a' + charIndex));
55 position += 2; // Skip one position to ensure no adjacent duplicates
56
57 // When even positions are filled, start filling odd positions
58 if (position >= stringLength) {
59 position = 1;
60 }
61 frequency--;
62 }
63 }
64
65 return result.toString();
66 }
67}
68
1class Solution {
2public:
3 string reorganizeString(string s) {
4 // Count frequency of each character
5 vector<int> charFrequency(26, 0);
6 for (char& ch : s) {
7 charFrequency[ch - 'a']++;
8 }
9
10 // Find maximum frequency
11 int maxFrequency = *max_element(charFrequency.begin(), charFrequency.end());
12 int stringLength = s.size();
13
14 // Check if reorganization is possible
15 // If any character appears more than (n+1)/2 times, it's impossible
16 if (maxFrequency > (stringLength + 1) / 2) {
17 return "";
18 }
19
20 // Create pairs of (frequency, character index) for sorting
21 vector<vector<int>> frequencyCharPairs;
22 for (int i = 0; i < 26; i++) {
23 if (charFrequency[i] > 0) {
24 frequencyCharPairs.push_back({charFrequency[i], i});
25 }
26 }
27
28 // Sort by frequency in descending order
29 sort(frequencyCharPairs.begin(), frequencyCharPairs.end());
30 reverse(frequencyCharPairs.begin(), frequencyCharPairs.end());
31
32 // Build result string by placing characters at alternating positions
33 string result = s; // Initialize with same length as input
34 int position = 0; // Start from position 0
35
36 // Place characters starting with highest frequency
37 for (auto& pair : frequencyCharPairs) {
38 int frequency = pair[0];
39 int charIndex = pair[1];
40
41 // Place this character 'frequency' times
42 while (frequency--) {
43 result[position] = 'a' + charIndex;
44 position += 2; // Skip one position to avoid adjacent same characters
45
46 // If we reach the end, start filling odd positions
47 if (position >= stringLength) {
48 position = 1;
49 }
50 }
51 }
52
53 return result;
54 }
55};
56
1function reorganizeString(s: string): string {
2 // Count frequency of each character
3 const charFrequency: number[] = new Array(26).fill(0);
4 for (const ch of s) {
5 charFrequency[ch.charCodeAt(0) - 'a'.charCodeAt(0)]++;
6 }
7
8 // Find maximum frequency
9 const maxFrequency: number = Math.max(...charFrequency);
10 const stringLength: number = s.length;
11
12 // Check if reorganization is possible
13 // If any character appears more than (n+1)/2 times, it's impossible
14 if (maxFrequency > Math.floor((stringLength + 1) / 2)) {
15 return "";
16 }
17
18 // Create pairs of [frequency, character index] for sorting
19 const frequencyCharPairs: number[][] = [];
20 for (let i = 0; i < 26; i++) {
21 if (charFrequency[i] > 0) {
22 frequencyCharPairs.push([charFrequency[i], i]);
23 }
24 }
25
26 // Sort by frequency in descending order
27 frequencyCharPairs.sort((a, b) => b[0] - a[0]);
28
29 // Build result string by placing characters at alternating positions
30 const result: string[] = new Array(stringLength); // Initialize array with same length as input
31 let position: number = 0; // Start from position 0
32
33 // Place characters starting with highest frequency
34 for (const pair of frequencyCharPairs) {
35 let frequency: number = pair[0];
36 const charIndex: number = pair[1];
37
38 // Place this character 'frequency' times
39 while (frequency--) {
40 result[position] = String.fromCharCode('a'.charCodeAt(0) + charIndex);
41 position += 2; // Skip one position to avoid adjacent same characters
42
43 // If we reach the end, start filling odd positions
44 if (position >= stringLength) {
45 position = 1;
46 }
47 }
48 }
49
50 return result.join('');
51}
52
Time and Space Complexity
Time Complexity: O(n log k)
where n
is the length of the string and k
is the number of unique characters (at most 26 for lowercase English letters).
- Creating the Counter takes
O(n)
time to count all characters - Finding the maximum value from the counter takes
O(k)
time - The
most_common()
method sorts the counter items, which takesO(k log k)
time - The nested while loop iterates through all characters exactly once, placing each character in the result array, which takes
O(n)
time total - The final join operation takes
O(n)
time
Overall: O(n) + O(k) + O(k log k) + O(n) + O(n) = O(n + k log k)
. Since k ≤ 26
for lowercase letters and k ≤ n
, this simplifies to O(n log k)
or effectively O(n)
when considering only lowercase English letters.
Space Complexity: O(n)
- The Counter object stores at most
k
key-value pairs, which takesO(k)
space - The answer array
ans
of sizen
takesO(n)
space - The final string created by join takes
O(n)
space
Overall: O(k) + O(n) + O(n) = O(n)
since k ≤ n
and the answer array dominates the space usage.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Validity Check Formula
A frequent mistake is using the wrong formula to check if reorganization is possible. Developers often write:
if max_frequency > n // 2:
(incorrect)if max_frequency >= (n + 1) // 2:
(incorrect)
The correct formula is if max_frequency > (n + 1) // 2:
. This accounts for both even and odd length strings:
- For even length (n=4): A character can appear at most 2 times
- For odd length (n=5): A character can appear at most 3 times
Solution: Always use (n + 1) // 2
as the threshold. This ceiling division ensures correct validation for both even and odd string lengths.
2. Not Handling the Index Reset Properly
When filling positions alternately, developers might reset the index incorrectly:
# Wrong approach current_index += 2 if current_index > string_length: # Should be >= not > current_index = 1
This off-by-one error can cause index out of bounds or skip valid positions.
Solution: Check if current_index >= string_length
to ensure you switch to odd indices at the right time.
3. Processing Characters in Wrong Order
Some implementations process characters randomly or in alphabetical order rather than by frequency:
# Wrong approach for character in char_frequency: # Processes in arbitrary order # Place character...
This can lead to invalid arrangements even when valid ones exist, especially when the most frequent character needs maximum spreading.
Solution: Always use char_frequency.most_common()
to process characters in descending frequency order. This ensures the most constrained characters (highest frequency) get optimal placement first.
4. Modifying Counter While Iterating
Attempting to modify the frequency counter while iterating over it:
# Wrong approach for character, frequency in char_frequency.most_common(): result[current_index] = character char_frequency[character] -= 1 # Modifying original counter
Solution: Use a local variable to track remaining frequency within the loop, as shown in the correct implementation with while frequency > 0: frequency -= 1
.
5. Using String Concatenation Instead of Array
Building the result using string concatenation:
# Inefficient approach result = "" # ... placement logic ... result += character # O(n) operation each time
This creates O(n²) time complexity due to string immutability in Python.
Solution: Use a list/array for O(1) position assignments, then join at the end with ''.join(result)
for O(n) overall string construction.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!