2287. Rearrange Characters to Make Target String
Problem Description
You are given two strings s
and target
, both indexed starting from 0. Your task is to determine how many complete copies of the string target
can be formed using the letters available in string s
.
The key points to understand:
- You can take any letters from
s
and rearrange them in any order - Each letter in
s
can only be used once - You need to form complete copies of
target
- partial copies don't count - You want to find the maximum number of complete copies possible
For example, if s = "ilovecodingonleetcode"
and target = "code"
, you need to check:
- How many 'c's are available in
s
versus needed intarget
- How many 'o's are available in
s
versus needed intarget
- How many 'd's are available in
s
versus needed intarget
- How many 'e's are available in
s
versus needed intarget
The limiting factor will be the character that has the smallest ratio of available letters to required letters. If s
has 6 'e's and target
needs 1 'e' per copy, you could make 6 copies based on 'e' alone. But if s
only has 2 'd's and target
needs 1 'd' per copy, you can only make 2 complete copies of target
.
The solution counts the frequency of each character in both strings, then for each character in target
, calculates how many times it can be used (by dividing available count by required count), and returns the minimum of these values.
Intuition
Think of this problem like a recipe. If you want to make cookies and the recipe calls for 2 cups of flour, 1 cup of sugar, and 3 eggs, how many batches can you make with your available ingredients? You'd check each ingredient: maybe you have 8 cups of flour (enough for 4 batches), 5 cups of sugar (enough for 5 batches), but only 6 eggs (enough for 2 batches). The eggs become your bottleneck - you can only make 2 complete batches.
The same logic applies here. Each character in target
is like an ingredient, and we need to figure out which character limits us the most.
We start by counting how many of each character we have in s
(our pantry) and how many of each character we need for one copy of target
(our recipe).
For each unique character in target
, we calculate: "How many times can I fulfill this character's requirement?" This is simply count_in_s // count_in_target
. The integer division gives us the maximum number of complete copies we can make based on just that character.
The key insight is that we need ALL characters to form a complete copy of target
. Just like in cooking, having excess of one ingredient doesn't help if another ingredient runs out. Therefore, the character that allows us to make the fewest copies determines our final answer.
This naturally leads us to take the minimum across all these ratios: min(cnt1[c] // cnt2[c] for each c in target)
. This minimum value represents the maximum number of complete copies of target
we can form.
Solution Approach
The implementation uses a counting approach with hash maps to efficiently solve this problem.
Step 1: Count character frequencies
We use Python's Counter
from the collections module to count the frequency of each character:
cnt1 = Counter(s)
creates a dictionary-like object where keys are characters froms
and values are their frequenciescnt2 = Counter(target)
does the same for thetarget
string
For example, if s = "aabbcc"
, then cnt1 = {'a': 2, 'b': 2, 'c': 2}
Step 2: Calculate maximum copies for each character
For each character c
that appears in target
(with frequency v
), we calculate how many complete copies we can make:
cnt1[c] // v
gives us the maximum number of times we can use characterc
- The integer division
//
ensures we only count complete copies
If a character in target
doesn't exist in s
, Counter
returns 0 by default, which results in 0 // v = 0
, correctly indicating we can't form any copies.
Step 3: Find the limiting factor
We use min()
to find the smallest ratio across all characters in target
:
return min(cnt1[c] // v for c, v in cnt2.items())
This generator expression iterates through each character-frequency pair (c, v)
in cnt2
, calculates the ratio, and returns the minimum value.
Time Complexity: O(m + n)
where m
is the length of s
and n
is the length of target
, as we need to count characters in both strings.
Space Complexity: O(1)
since we're only storing character counts and there are at most 26 lowercase English letters (constant space).
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 a concrete example to illustrate the solution approach.
Example: s = "abbaccaddeebc"
and target = "abcd"
Step 1: Count character frequencies
First, we count how many times each character appears in both strings:
s = "abbaccaddeebc"
→cnt1 = {'a': 3, 'b': 3, 'c': 3, 'd': 2, 'e': 2}
target = "abcd"
→cnt2 = {'a': 1, 'b': 1, 'c': 1, 'd': 1}
Step 2: Calculate maximum copies for each character
Now we check how many copies of target
we can make based on each character:
-
Character 'a': We have 3 'a's in
s
, need 1 'a' per copy oftarget
- Maximum copies based on 'a':
3 // 1 = 3
- Maximum copies based on 'a':
-
Character 'b': We have 3 'b's in
s
, need 1 'b' per copy oftarget
- Maximum copies based on 'b':
3 // 1 = 3
- Maximum copies based on 'b':
-
Character 'c': We have 3 'c's in
s
, need 1 'c' per copy oftarget
- Maximum copies based on 'c':
3 // 1 = 3
- Maximum copies based on 'c':
-
Character 'd': We have 2 'd's in
s
, need 1 'd' per copy oftarget
- Maximum copies based on 'd':
2 // 1 = 2
- Maximum copies based on 'd':
Step 3: Find the limiting factor
The minimum of all these ratios is: min(3, 3, 3, 2) = 2
The character 'd' is our bottleneck - we can only make 2 complete copies of "abcd" because we only have 2 'd's available.
Verification:
- First copy uses: 1 'a', 1 'b', 1 'c', 1 'd' (remaining: 2 'a's, 2 'b's, 2 'c's, 1 'd')
- Second copy uses: 1 'a', 1 'b', 1 'c', 1 'd' (remaining: 1 'a', 1 'b', 1 'c', 0 'd's)
- Cannot make a third copy because we're out of 'd's
Therefore, the answer is 2.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def rearrangeCharacters(self, s: str, target: str) -> int:
5 # Count frequency of each character in the source string
6 source_char_count = Counter(s)
7
8 # Count frequency of each character in the target string
9 target_char_count = Counter(target)
10
11 # Calculate the maximum number of times we can form the target string
12 # For each character in target, divide source count by target count
13 # The minimum ratio determines how many complete target strings we can form
14 max_copies = min(source_char_count[char] // count
15 for char, count in target_char_count.items())
16
17 return max_copies
18
1class Solution {
2 public int rearrangeCharacters(String s, String target) {
3 // Array to store frequency of each character in string s
4 int[] sourceCharCount = new int[26];
5 // Array to store frequency of each character in target string
6 int[] targetCharCount = new int[26];
7
8 // Count frequency of each character in source string s
9 for (int i = 0; i < s.length(); i++) {
10 sourceCharCount[s.charAt(i) - 'a']++;
11 }
12
13 // Count frequency of each character in target string
14 for (int i = 0; i < target.length(); i++) {
15 targetCharCount[target.charAt(i) - 'a']++;
16 }
17
18 // Initialize maximum possible copies to a large number
19 int maxCopies = Integer.MAX_VALUE;
20
21 // For each character in the alphabet
22 for (int i = 0; i < 26; i++) {
23 // If this character is required in the target string
24 if (targetCharCount[i] > 0) {
25 // Calculate how many copies of target can be formed with this character
26 // Update the minimum (bottleneck character determines max copies)
27 maxCopies = Math.min(maxCopies, sourceCharCount[i] / targetCharCount[i]);
28 }
29 }
30
31 return maxCopies;
32 }
33}
34
1class Solution {
2public:
3 int rearrangeCharacters(string s, string target) {
4 // Array to store frequency of each character in string s
5 int sourceFreq[26]{};
6 // Array to store frequency of each character in target string
7 int targetFreq[26]{};
8
9 // Count frequency of each character in source string s
10 for (char& c : s) {
11 ++sourceFreq[c - 'a'];
12 }
13
14 // Count frequency of each character in target string
15 for (char& c : target) {
16 ++targetFreq[c - 'a'];
17 }
18
19 // Initialize result with a large value (maximum possible copies)
20 int maxCopies = 100;
21
22 // For each character in the alphabet
23 for (int i = 0; i < 26; ++i) {
24 // If this character exists in target string
25 if (targetFreq[i] > 0) {
26 // Calculate how many copies of target can be formed using this character
27 // Update the minimum (bottleneck character determines max copies)
28 maxCopies = min(maxCopies, sourceFreq[i] / targetFreq[i]);
29 }
30 }
31
32 return maxCopies;
33 }
34};
35
1/**
2 * Calculates the maximum number of times the target string can be formed
3 * using characters from the source string s
4 * @param s - The source string containing available characters
5 * @param target - The target string to be formed
6 * @returns The maximum number of times target can be formed
7 */
8function rearrangeCharacters(s: string, target: string): number {
9 // Helper function to convert character to array index (0-25 for a-z)
10 const charToIndex = (char: string): number => char.charCodeAt(0) - 97;
11
12 // Array to store frequency of each character in source string
13 const sourceCharCount: number[] = new Array(26).fill(0);
14
15 // Array to store frequency of each character in target string
16 const targetCharCount: number[] = new Array(26).fill(0);
17
18 // Count frequency of each character in source string
19 for (const char of s) {
20 sourceCharCount[charToIndex(char)]++;
21 }
22
23 // Count frequency of each character in target string
24 for (const char of target) {
25 targetCharCount[charToIndex(char)]++;
26 }
27
28 // Initialize result with a large number (upper bound)
29 let maxCopies: number = 100;
30
31 // Check each character that appears in target
32 for (let i = 0; i < 26; i++) {
33 if (targetCharCount[i] > 0) {
34 // Calculate how many times we can form target based on this character
35 const possibleCopies: number = Math.floor(sourceCharCount[i] / targetCharCount[i]);
36 // Update minimum (bottleneck determines the result)
37 maxCopies = Math.min(maxCopies, possibleCopies);
38 }
39 }
40
41 return maxCopies;
42}
43
Time and Space Complexity
The time complexity is O(n + m)
, where n
is the length of string s
and m
is the length of string target
. This is because:
- Creating
Counter(s)
requires iterating through alln
characters of strings
, takingO(n)
time - Creating
Counter(target)
requires iterating through allm
characters of stringtarget
, takingO(m)
time - The
min()
operation iterates through all unique characters intarget
, which is at mostm
characters, takingO(m)
time - Therefore, the total time complexity is
O(n) + O(m) + O(m) = O(n + m)
The space complexity is O(|Σ|)
, where |Σ|
represents the size of the character set. In this problem, since we're dealing with lowercase English letters, |Σ| = 26
. This is because:
Counter(s)
stores at most26
unique lowercase letters and their frequenciesCounter(target)
stores at most26
unique lowercase letters and their frequencies- Both counters are bounded by the alphabet size rather than the input size
- Therefore, the space complexity is
O(26) + O(26) = O(|Σ|)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Missing Characters Properly
A common mistake is forgetting that if a character in target
doesn't exist in s
at all, the function should return 0 immediately. While Python's Counter
handles this gracefully by returning 0 for missing keys, manually implementing with regular dictionaries can cause KeyError.
Incorrect approach:
def rearrangeCharacters(self, s: str, target: str) -> int:
char_count = {}
for char in s:
char_count[char] = char_count.get(char, 0) + 1
min_copies = float('inf')
for char in target:
# This will cause KeyError if char not in char_count
min_copies = min(min_copies, char_count[char]) # Wrong!
return min_copies
Solution: Always check if the character exists or use .get()
with a default value:
for char in target:
min_copies = min(min_copies, char_count.get(char, 0))
2. Forgetting to Account for Character Frequency in Target
Another pitfall is only checking if characters exist but not considering how many times each character appears in target
. For example, if target = "aaa"
, you need 3 'a's for each copy, not just 1.
Incorrect approach:
def rearrangeCharacters(self, s: str, target: str) -> int:
source_count = Counter(s)
# Wrong: only checking unique characters in target
return min(source_count[char] for char in set(target))
Solution: Count the frequency of each character in target
and divide appropriately:
target_count = Counter(target)
return min(source_count[char] // count for char, count in target_count.items())
3. Using Regular Division Instead of Integer Division
Using /
instead of //
will return a float, which might seem correct but can lead to issues when the result should be an integer count of complete copies.
Incorrect approach:
# This returns a float
max_copies = min(source_count[char] / count for char, count in target_count.items())
return int(max_copies) # Converting at the end can introduce rounding errors
Solution: Use integer division //
from the start to ensure clean integer arithmetic:
max_copies = min(source_count[char] // count for char, count in target_count.items())
4. Edge Case: Empty Target String
If target
is empty, the generator expression will have no items to iterate over, causing min()
to raise a ValueError.
Solution: Add a guard clause:
def rearrangeCharacters(self, s: str, target: str) -> int:
if not target:
return 0 # or handle as per problem requirements
source_count = Counter(s)
target_count = Counter(target)
return min(source_count[char] // count for char, count in target_count.items())
Which of the following uses divide and conquer strategy?
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!