1897. Redistribute Characters to Make All Strings Equal
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
andj
words[i]
must be a non-empty string- Move any single character from
words[i]
to any position inwords[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
.
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 valuev
in the counter is divisible byn
- The modulo operator
%
returns 0 ifv
is perfectly divisible byn
all()
returnsTrue
only if all character counts are divisible byn
, otherwiseFalse
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 EvaluatorExample 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.
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!