Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Count character frequencies: Use a Counter to count occurrences of each character in the string. This gives us cnt where cnt[char] is the frequency of that character.

  2. Check validity: Find the maximum frequency among all characters with mx = max(cnt.values()). If mx > (n + 1) // 2, return an empty string since it's impossible to arrange the characters without adjacency.

  3. Initialize the result array: Create an array ans of size n to build our rearranged string. Using an array allows O(1) access to any position.

  4. 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.

  5. Alternating placement algorithm:

    • Start with index i = 0 (first even position)
    • For each character k with frequency v:
      • 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 setting i = 1

    This creates the pattern: positions 0, 2, 4, 6... then 1, 3, 5, 7...

  6. 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 Evaluator

Example 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 '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 'c' (1 time):

    • Place 'c' at position 5: ans = [a, b, a, b, a, c], i = 7

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 takes O(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 takes O(k) space
  • The answer array ans of size n takes O(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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More