GreedyProblem 34 of 35

Rearrange characters in a string such that no two adjacent are same

Brute Force
Time: O(n!)
Space: O(n)
Optimal
Time: O(n log n)
Space: O(n)

Problem Statement

Given a string with repeated characters, rearrange the characters such that no two adjacent characters are the same. If not possible, return an empty string.

Constraints:

  • 1 ≤ length ≤ 10^5
  • String contains lowercase English letters

Example:

  • Input: s = "aab"
  • Output: "aba" or "baa"... wait, "baa" has adjacent 'a's. Output: "aba"

Example 2:

  • Input: s = "aaab"
  • Output: "" (impossible - 3 a's can't be separated with only 1 b)

Example 3:

  • Input: s = "aabb"
  • Output: "abab" or "baba"

Approach 1: Brute Force (Try All Permutations)

Intuition

Generate all permutations of the string and find one where no two adjacent characters are the same.

Algorithm

  1. Generate all permutations of the string
  2. For each permutation, check if valid (no adjacent same characters)
  3. Return first valid permutation or empty string if none exists
java
import java.util.*;

public class Solution {
    private boolean isValid(String s) {
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == s.charAt(i-1)) return false;
        }
        return true;
    }
    
    public String rearrangeStringBrute(String s) {
        char[] chars = s.toCharArray();
        Arrays.sort(chars);
        
        return permute(chars, 0);
    }
    
    private String permute(char[] chars, int start) {
        if (start == chars.length) {
            String s = new String(chars);
            return isValid(s) ? s : null;
        }
        
        for (int i = start; i < chars.length; i++) {
            swap(chars, start, i);
            String result = permute(chars, start + 1);
            if (result != null) return result;
            swap(chars, start, i);
        }
        
        return null;
    }
    
    private void swap(char[] chars, int i, int j) {
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
}

Complexity Analysis

  • Time Complexity: O(n!) - All permutations
  • Space Complexity: O(n) - For storing permutation

Approach 2: Greedy with Max-Heap (Optimal)

Intuition

Always place the character with the highest remaining frequency, but not the same as the last placed character. Use a max-heap to efficiently get the most frequent character.

Key Insight: A valid arrangement exists if and only if no character appears more than (n+1)/2 times.

Algorithm

  1. Count frequency of each character
  2. Check if any character exceeds (n+1)/2 - if yes, return ""
  3. Use max-heap based on frequency
  4. Always pick the most frequent character different from the last placed
  5. Build result string
java
import java.util.*;

public class Solution {
    public String rearrangeString(String s) {
        // Count frequencies
        Map<Character, Integer> freq = new HashMap<>();
        for (char c : s.toCharArray()) {
            freq.put(c, freq.getOrDefault(c, 0) + 1);
        }
        
        // Check if valid
        int n = s.length();
        for (int count : freq.values()) {
            if (count > (n + 1) / 2) {
                return "";
            }
        }
        
        // Max-heap based on frequency
        PriorityQueue<int[]> maxHeap = new PriorityQueue<>(
            (a, b) -> b[0] - a[0]
        );
        for (Map.Entry<Character, Integer> e : freq.entrySet()) {
            maxHeap.offer(new int[]{e.getValue(), e.getKey()});
        }
        
        StringBuilder result = new StringBuilder();
        int[] prev = {0, '#'};
        
        while (!maxHeap.isEmpty()) {
            int[] curr = maxHeap.poll();
            result.append((char)curr[1]);
            
            if (prev[0] > 0) {
                maxHeap.offer(prev);
            }
            
            prev = new int[]{curr[0] - 1, curr[1]};
        }
        
        return result.toString();
    }
    
    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.rearrangeString("aab"));   // Output: "aba"
        System.out.println(sol.rearrangeString("aaab"));  // Output: ""
        System.out.println(sol.rearrangeString("aabb"));  // Output: "abab"
    }
}

Dry Run Example

s = "aaabbc" n = 6, max allowed frequency = (6+1)/2 = 3 Frequencies: a=3, b=2, c=1 Max frequency = 3 ≤ 3, so valid arrangement exists Max-heap: [(3,a), (2,b), (1,c)] result = "", prev = (0, #) Iteration 1: - Pop (3,a), result = "a" - prev (0,#) has count 0, don't push - prev = (2,a) - heap = [(2,b), (1,c)] Iteration 2: - Pop (2,b), result = "ab" - Push prev (2,a) to heap - prev = (1,b) - heap = [(2,a), (1,c)] Iteration 3: - Pop (2,a), result = "aba" - Push prev (1,b) to heap - prev = (1,a) - heap = [(1,b), (1,c)] Iteration 4: - Pop (1,b), result = "abab" - Push prev (1,a) to heap - prev = (0,b) - heap = [(1,a), (1,c)] Iteration 5: - Pop (1,a), result = "ababa" - prev (0,b) has count 0, don't push - prev = (0,a) - heap = [(1,c)] Iteration 6: - Pop (1,c), result = "ababac" - prev (0,a) has count 0, don't push - prev = (0,c) - heap = [] Result: "ababac" Verification: No adjacent characters are same ✓

Complexity Analysis

  • Time Complexity: O(n log n) - n heap operations, each O(log 26) ≈ O(log n)
  • Space Complexity: O(n) - For result string and heap

Approach 3: Fill Even and Odd Positions

Intuition

Place the most frequent character at even indices first, then fill remaining positions.


Edge Cases


Key Takeaways

  1. Validity Condition: No character should appear more than (n+1)/2 times
  2. Greedy Choice: Always place the most frequent character (if different from last)
  3. Max-Heap: Efficiently tracks the most frequent available character
  4. Alternate Strategy: Fill even positions first, then odd positions
  5. Proof of Correctness: If valid, greedy always produces a valid arrangement
  6. Similar Problems: Task scheduler, reorganize string with k distance
  7. LeetCode Reference: LeetCode 767 - Reorganize String