Facebook Pixel

1897. Redistribute Characters to Make All Strings Equal

EasyHash TableStringCounting
Leetcode Link

Problem Description

You have an array of strings called words (0-indexed).

You can perform operations to move characters between strings. In each operation:

  • Pick two different indices i and j
  • words[i] must be a non-empty string
  • Move any single character from words[i] to any position in words[j]

The goal is to determine if it's possible to make all strings in the array equal by performing any number of these operations.

Return true if you can make every string in words equal, and false otherwise.

The key insight is that you can redistribute characters freely between strings. For all strings to become equal, each unique character in the entire collection must appear a total number of times that can be evenly divided among all strings.

For example, if you have 3 strings and the character 'a' appears 6 times total across all strings, then each final equal string could have exactly 2 'a's. But if 'a' appears 7 times total, it's impossible to distribute evenly among 3 strings.

The solution counts the total occurrences of each character across all strings using a Counter. Then it checks if each character count is divisible by the number of strings (n). If all character counts are divisible by n, return true; otherwise return false.

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

Intuition

When we can move characters freely between strings, we need to think about what the final state would look like if all strings become equal.

If all strings end up being identical, and we have n strings total, then each string must contain exactly the same characters in the same quantities. This means that for any character that appears in our collection of strings, its total count must be perfectly divisible by n.

Think of it like distributing items equally among people - if you have 12 apples and 3 people, each person gets exactly 4 apples. But if you have 13 apples and 3 people, there's no way to distribute them equally without splitting an apple.

The same logic applies here. Since we can move characters around freely (but cannot create or destroy them), the total pool of characters remains constant. For example:

  • If we have ["abc", "aabc", "bc"] (3 strings)
  • Character 'a' appears 3 times total → 3 ÷ 3 = 1 per string ✓
  • Character 'b' appears 3 times total → 3 ÷ 3 = 1 per string ✓
  • Character 'c' appears 3 times total → 3 ÷ 3 = 1 per string ✓
  • All strings can become "abc"

But if we have ["ab", "a"] (2 strings):

  • Character 'a' appears 2 times → 2 ÷ 2 = 1 per string ✓
  • Character 'b' appears 1 time → 1 ÷ 2 = 0.5 per string ✗
  • Impossible to make equal strings

Therefore, the solution is straightforward: count all characters across all strings, then check if each character's count is divisible by the number of strings.

Solution Approach

The implementation uses a counting approach with a hash table to track character frequencies across all strings.

Step 1: Count all characters

We initialize a Counter object (which is a specialized dictionary for counting) to store the frequency of each character across all strings:

cnt = Counter()
for w in words:
    for c in w:
        cnt[c] += 1

This nested loop iterates through each word w in the words array, then through each character c in that word, incrementing the count for that character. After this step, cnt contains the total occurrences of each unique character.

Step 2: Check divisibility

We need to verify that each character's count can be evenly distributed among all strings:

n = len(words)
return all(v % n == 0 for v in cnt.values())
  • First, we get the total number of strings n
  • Then we use Python's all() function with a generator expression to check if every value v in the counter is divisible by n
  • The modulo operator % returns 0 if v is perfectly divisible by n
  • all() returns True only if all character counts are divisible by n, otherwise False

Time Complexity: O(m) where m is the total number of characters across all strings (sum of lengths of all words)

Space Complexity: O(k) where k is the number of unique characters (at most 26 for lowercase English letters)

The elegance of this solution lies in its simplicity - instead of simulating the actual character movements, we directly check the mathematical condition that must be satisfied for equal distribution to be possible.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with a concrete example: words = ["abc", "aabc", "bc"]

Step 1: Count all characters across all strings

We'll go through each word and count every character:

  • Word "abc": a=1, b=1, c=1
  • Word "aabc": a=2, b=1, c=1
  • Word "bc": b=1, c=1

Total counts: {'a': 3, 'b': 3, 'c': 3}

Step 2: Check if each count is divisible by the number of strings

We have n = 3 strings total.

Now check each character:

  • 'a' appears 3 times: 3 % 3 = 0 ✓ (can give 1 'a' to each string)
  • 'b' appears 3 times: 3 % 3 = 0 ✓ (can give 1 'b' to each string)
  • 'c' appears 3 times: 3 % 3 = 0 ✓ (can give 1 'c' to each string)

Since all character counts are divisible by 3, we return true. Each string can become "abc".

Counter-example: words = ["ab", "a"]

Count characters:

  • Total counts: {'a': 2, 'b': 1}
  • We have n = 2 strings

Check divisibility:

  • 'a' appears 2 times: 2 % 2 = 0
  • 'b' appears 1 time: 1 % 2 = 1 ✗ (cannot split 1 'b' evenly between 2 strings)

Since 'b' cannot be evenly distributed, we return false.

Solution Implementation

1from collections import Counter
2from typing import List
3
4class Solution:
5    def makeEqual(self, words: List[str]) -> bool:
6        # Count frequency of each character across all words
7        char_count = Counter()
8      
9        # Iterate through each word and count characters
10        for word in words:
11            for char in word:
12                char_count[char] += 1
13      
14        # Get the number of words
15        num_words = len(words)
16      
17        # Check if each character count is divisible by number of words
18        # This ensures characters can be distributed equally among all words
19        return all(count % num_words == 0 for count in char_count.values())
20
1class Solution {
2    /**
3     * Determines if all strings in the array can be made equal by redistributing characters.
4     * Characters can be moved between strings to make all strings identical.
5     * 
6     * @param words Array of strings to check
7     * @return true if all strings can be made equal, false otherwise
8     */
9    public boolean makeEqual(String[] words) {
10        // Array to store frequency count of each character (a-z)
11        int[] characterCount = new int[26];
12      
13        // Count the frequency of each character across all words
14        for (String word : words) {
15            for (char character : word.toCharArray()) {
16                // Increment count for this character (using 'a' as base index 0)
17                characterCount[character - 'a']++;
18            }
19        }
20      
21        // Get the number of words in the array
22        int numberOfWords = words.length;
23      
24        // Check if each character's total count is divisible by the number of words
25        // This ensures equal distribution is possible
26        for (int count : characterCount) {
27            if (count % numberOfWords != 0) {
28                // If any character count is not evenly divisible, equal distribution is impossible
29                return false;
30            }
31        }
32      
33        // All characters can be evenly distributed among all words
34        return true;
35    }
36}
37
1class Solution {
2public:
3    bool makeEqual(vector<string>& words) {
4        // Array to store frequency count of each character (a-z)
5        int charFrequency[26]{};
6      
7        // Count occurrences of each character across all words
8        for (const auto& word : words) {
9            for (const auto& character : word) {
10                // Increment count for this character (normalize 'a' to index 0)
11                ++charFrequency[character - 'a'];
12            }
13        }
14      
15        // Get the total number of words
16        int wordCount = words.size();
17      
18        // Check if each character's frequency is divisible by word count
19        // For equal distribution, each character must appear n*k times (k can be 0)
20        for (int i = 0; i < 26; ++i) {
21            if (charFrequency[i] % wordCount != 0) {
22                // Cannot distribute this character equally among all words
23                return false;
24            }
25        }
26      
27        // All characters can be distributed equally
28        return true;
29    }
30};
31
1/**
2 * Checks if all strings in the array can be made equal by redistributing characters
3 * @param words - Array of strings to check
4 * @returns true if characters can be redistributed to make all strings equal, false otherwise
5 */
6function makeEqual(words: string[]): boolean {
7    // Count frequency of each character across all words
8    const characterCount: Record<string, number> = {};
9  
10    // Iterate through each word in the array
11    for (const word of words) {
12        // Count each character in the current word
13        for (const character of word) {
14            characterCount[character] = (characterCount[character] || 0) + 1;
15        }
16    }
17  
18    // Get the total number of words
19    const totalWords = words.length;
20  
21    // Check if each character count is divisible by the number of words
22    // If all characters can be evenly distributed, all words can be made equal
23    return Object.values(characterCount).every(count => count % totalWords === 0);
24}
25

Time and Space Complexity

The time complexity is O(L), where L is the total length of all strings in the array words. This is because the algorithm iterates through each word in the list and then through each character in that word exactly once to count the frequency of each character. The final check all(v % n == 0 for v in cnt.values()) iterates through at most 26 values (the size of the alphabet), which is O(1) relative to the input size.

The space complexity is O(|Σ|), where Σ is the character set (lowercase English letters), so |Σ| = 26. The Counter object cnt stores at most 26 key-value pairs, one for each distinct lowercase letter that appears in the input strings. This space requirement is constant and independent of the input size L.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting Edge Cases with Empty Words Array

A common mistake is not considering what happens when the input array is empty or contains only one string.

Pitfall Example:

def makeEqual(self, words: List[str]) -> bool:
    char_count = Counter()
    for word in words:
        for char in word:
            char_count[char] += 1
  
    num_words = len(words)  # Could be 0!
    return all(count % num_words == 0 for count in char_count.values())

Issue: If words is empty, num_words becomes 0, leading to a division by zero error when checking count % 0.

Solution:

def makeEqual(self, words: List[str]) -> bool:
    # Handle edge cases
    if len(words) <= 1:
        return True  # Empty array or single word is already "equal"
  
    char_count = Counter()
    for word in words:
        for char in word:
            char_count[char] += 1
  
    num_words = len(words)
    return all(count % num_words == 0 for count in char_count.values())

2. Misunderstanding the Problem - Thinking Order Matters

Some might incorrectly assume that the final equal strings need to maintain character order or that characters can only be moved to specific positions.

Pitfall Thinking: "If I have ['abc', 'def'], I can't make them equal because 'a' can't become 'd'"

Correct Understanding: Characters can be freely redistributed. With ['abc', 'def'], we have 1 of each character (a,b,c,d,e,f), and since we can't divide 1 evenly between 2 strings, the answer is false. But if we had ['abc', 'bca'], we have 2 a's, 2 b's, 2 c's, which can be distributed to form ['abc', 'abc'].

3. Using Wrong Data Structure or Manual Counting

Attempting to manually track character counts with a regular dictionary or array can lead to errors.

Pitfall Example:

def makeEqual(self, words: List[str]) -> bool:
    char_count = {}
    for word in words:
        for char in word:
            if char not in char_count:
                char_count[char] = 0  # Forgot to initialize to 0
            char_count[char] += 1
    # ... rest of code

Issue: Manual dictionary management is error-prone and verbose.

Solution: Use Counter from collections, which handles initialization automatically and provides cleaner code.

4. Incorrect Divisibility Check Logic

Using the wrong condition for checking if redistribution is possible.

Pitfall Example:

def makeEqual(self, words: List[str]) -> bool:
    # ... counting logic ...
  
    # Wrong: Checking if any (instead of all) counts are divisible
    return any(count % num_words == 0 for count in char_count.values())

Issue: Using any() instead of all() would return true if even one character can be evenly distributed, which is incorrect. ALL characters must be evenly distributable.

Solution: Always use all() to ensure every character count is divisible by the number of words.

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

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

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

Load More