2287. Rearrange Characters to Make Target String
Problem Description
The task is to determine how many copies of the given target
string can be formed using characters from the string s
. We are allowed to rearrange the characters taken from s
to match the order of characters in target
. Both strings s
and target
start indexing from 0. In essence, remember that you can only form a complete target
if all the characters in target
can be matched by characters from s
, considering also the number of occurrences.
For example, if s
is "abcab" and target
is "ab", you can form two copies of target
because s
contains two 'a's and two 'b's, which are enough to form "ab" twice.
Intuition
The solution relies on a simple insight: For each unique character in target
, count how many times we can find it within s
. This is essentially a problem of matching supply with demand. The "supply" here is the number of times a character appears in s
, and the "demand" is the number of times it appears in target
.
To solve the problem, we perform the following steps:
- Count the occurrences of each character in
s
. This gives us the supply for each character available ins
. - Count the occurrences of each character in
target
. This represents the demand for each character to formulate atarget
string. - For each character in
target
, calculate how many times we can provide for that demand using our supply. This is done by dividing the supply of a character ins
by the demand of that character intarget
. - The minimum of these quotients (rounded down) is the maximum number of times we can form the
target
string. This is because we cannot form a completetarget
string if even one character is in short supply.
For the final answer, we can use Python's min
function to find this minimum quotient for all characters. The Counter
class from the collections
module is ideal for counting character occurrences efficiently.
Here is the description of the solution in Python code:
class Solution:
def rearrangeCharacters(self, s: str, target: str) -> int:
cnt1 = Counter(s) # Count characters in s
cnt2 = Counter(target) # Count characters in target
# Calculate min number of times we can form target character-wise
return min(cnt1[c] // v for c, v in cnt2.items())
Using Counter(s)
and Counter(target)
, we create two dictionaries to store the counts of each character. We then use a generator expression inside the min
function to calculate the minimum number of complete copies of target
that can be formed. This generator expression iterates over each character and its count in target
, using //
for integer division to find how many times each character in target
can be supplied by s
. The min
function then determines the maximum copies of target
by finding the smallest of these values.
Solution Approach
The solution implements a straightforward approach using two algorithms/data structures:
-
Counters (Hash Tables): The Python
Counter
class from thecollections
module serves as a hash table or a dictionary that maps each unique character to its count. This data structure is used twice: once for counting occurrences of characters ins
and once fortarget
. This allows us to have immediate access to the count of each character required and available. -
Integer Division: The Python
//
operator is used for integer division. It provides the quotient of the division without the remainder, which is crucial since we're interested in the number of complete copies oftarget
that can be formed.
Here's a breakdown of the implementation steps:
-
cnt1 = Counter(s)
: We count each character ins
.Counter
iterates overs
, and for each character, it increments the character's count in the hash table. For example, ifs = "bbaac"
, thencnt1
will be{ 'b': 2, 'a': 2, 'c': 1 }
. -
cnt2 = Counter(target)
: Similarly, we count the characters intarget
. Iftarget = "abc"
, thencnt2
will be{ 'a': 1, 'b': 1, 'c': 1 }
. -
The generator expression
(cnt1[c] // v for c, v in cnt2.items())
iterates over the items ofcnt2
, which are tuples of (character, count) fromtarget
. For each characterc
intarget
, it calculates how many times that character can be taken froms
. This is the supply we have (fromcnt1[c]
) divided by the demand (fromv
). -
min(...)
: We wrap the generator expression withmin
to find the character that is the limiting factor in forming thetarget
. This step ensures that we consider the "least abundant" character in terms of how many completetarget
strings we can formulate. The minimum value is the maximum number oftarget
strings we can form.
Here's the code section again for reference:
class Solution:
def rearrangeCharacters(self, s: str, target: str) -> int:
cnt1 = Counter(s) # Count characters in s
cnt2 = Counter(target) # Count characters in target
return min(cnt1[c] // v for c, v in cnt2.items())
This implementation successfully leverages the strength of hash tables for counting operations, combined with integer division to satisfy the requirements of the problem efficiently.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's go through an example to illustrate the solution approach. Suppose we are given the string s = "aabbbcc"
and target = "abc"
. Our task is to determine the maximum number of copies of target
that can be formed from s
.
Step 1: First, we use Counter(s)
to count the occurrences of each character in s
. This gives us a dictionary cnt1
which represents our character supply:
cnt1 = Counter("aabbbcc")
results incnt1 = {'a': 2, 'b': 3, 'c': 2}
.
Step 2: Next, we count the occurrences of each character in target
. This gives us another dictionary cnt2
which represents our character demand:
cnt2 = Counter("abc")
results incnt2 = {'a': 1, 'b': 1, 'c': 1}
.
Step 3: We then iterate over the cnt2
dictionary and for each character in target
, we calculate how many times the character can be provided by the supply from s
. This is done by dividing cnt1[c]
by cnt2[c]
:
- For character 'a': the supply is
cnt1['a'] = 2
and the demand iscnt2['a'] = 1
, so2 // 1 = 2
. We can form two 'a's. - For character 'b': the supply is
cnt1['b'] = 3
and the demand iscnt2['b'] = 1
, so3 // 1 = 3
. We can form three 'b's. - For character 'c': the supply is
cnt1['c'] = 2
and the demand iscnt2['c'] = 1
, so2 // 1 = 2
. We can form two 'c's.
Step 4: The final step is to find the minimum value among the quotients calculated because the minimum value represents the bottleneck for forming the target
string fully:
- We take the minimum value of
[2, 3, 2]
, which corresponds to the counts of 'a,' 'b,' and 'c' respectively. - The minimum is
2
, so we can form thetarget
stringabc
twice using characters froms
.
Hence, the result of running our solution would be 2
as that is the maximum number of times we can completely form the string abc
from the characters in s = "aabbbcc"
.
Solution Implementation
1from collections import Counter # Import the Counter class from collections module
2
3class Solution:
4 def rearrangeCharacters(self, s: str, target: str) -> int:
5 # Count the frequency of each character in the original string `s`
6 char_count_original = Counter(s)
7 # Count the frequency of each character in the `target` string
8 char_count_target = Counter(target)
9
10 # Calculate the minimum number of times the `target` can be formed
11 # by dividing the frequency of each character in `s` by the
12 # frequency of the same character in `target`, and taking the min.
13 return min(char_count_original[char] // count for char, count in char_count_target.items())
14
15# The function rearrangeCharacters takes two strings as parameters: 's' and 'target'.
16# It utilizes the Counter class to count occurrences of each character.
17# Then, it determines the smallest quotient of the character counts from 's' divided by those from 'target',
18# which represents the maximum number of times 'target' can be formed from 's'.
19
1class Solution {
2 public int rearrangeCharacters(String s, String target) {
3 // counts for characters in 's'
4 int[] countS = new int[26];
5 // counts for characters in 'target'
6 int[] countTarget = new int[26];
7
8 // Count frequency of each character in 's'
9 for (int i = 0; i < s.length(); ++i) {
10 countS[s.charAt(i) - 'a']++;
11 }
12
13 // Count frequency of each character in 'target'
14 for (int i = 0; i < target.length(); ++i) {
15 countTarget[target.charAt(i) - 'a']++;
16 }
17
18 // Initialize the answer with a high value.
19 // It represents the maximum number of times 'target' can be formed.
20 int maxFormable = Integer.MAX_VALUE;
21
22 // Calculate the number of times 'target' can be formed
23 for (int i = 0; i < 26; ++i) {
24 if (countTarget[i] > 0) {
25 // Find the minimum number of times a character from 'target'
26 // can be used based on its frequency in 's'
27 maxFormable = Math.min(maxFormable, countS[i] / countTarget[i]);
28 }
29 }
30
31 // Return the maximum number of times 'target' can be formed
32 return maxFormable;
33 }
34}
35
1#include <string>
2#include <algorithm>
3
4class Solution {
5public:
6 // This function calculates the maximum number of times the target string
7 // can be formed using the characters from the string s.
8 int rearrangeCharacters(string s, string target) {
9 // Initialize character count arrays for s and target strings.
10 int countS[26] = {0};
11 int countTarget[26] = {0};
12
13 // Count the frequency of each character in the string s.
14 for (char ch : s) {
15 ++countS[ch - 'a']; // Update the count of the character ch.
16 }
17
18 // Count the frequency of each character in the target string.
19 for (char ch : target) {
20 ++countTarget[ch - 'a']; // Update the count of the character ch.
21 }
22
23 // An arbitrary large value chosen as a starting minimum.
24 int maxOccurrences = INT_MAX; // Can make the target string at least this many times.
25
26 // Loop through each character from 'a' to 'z'.
27 for (int i = 0; i < 26; ++i) {
28 // If the current character is in the target string,
29 // find the minimum number of times we can use it by comparing
30 // the frequency of the character in s and target.
31 if (countTarget[i]) {
32 maxOccurrences = std::min(maxOccurrences, countS[i] / countTarget[i]);
33 }
34 }
35
36 // Return the maximum number of times we can form the target string.
37 return maxOccurrences;
38 }
39};
40
1function rearrangeCharacters(source: string, target: string): number {
2 // Helper function to get the index using character code.
3 // Assumes that the input characters will be lowercase English alphabets.
4 const getIndex = (character: string) => character.charCodeAt(0) - 'a'.charCodeAt(0);
5
6 // Counts of characters in the source string.
7 const sourceCount = new Array(26).fill(0);
8 // Counts of characters in the target string.
9 const targetCount = new Array(26).fill(0);
10
11 // Count occurrences of each character in the source string.
12 for (const character of source) {
13 ++sourceCount[getIndex(character)];
14 }
15
16 // Count occurrences of each character in the target string.
17 for (const character of target) {
18 ++targetCount[getIndex(character)];
19 }
20
21 // Initialize the answer to the highest possible number.
22 // This value will eventually hold the maximum number of times
23 // the 'target' can be formed from 'source'.
24 let maxTargetCount = Infinity;
25
26 // Loop through each letter's count in the target string
27 // and calculate how many times the target can be created
28 // from the source based on the minimum availability of
29 // required characters in the source string.
30 for (let i = 0; i < 26; ++i) {
31 if (targetCount[i]) {
32 maxTargetCount = Math.min(maxTargetCount, Math.floor(sourceCount[i] / targetCount[i]));
33 }
34 }
35
36 // Return the maximum number of times the target can be formed.
37 // If the target cannot be formed even once, it will return 0.
38 return maxTargetCount === Infinity ? 0 : maxTargetCount;
39}
40
Time and Space Complexity
The given Python code snippet aims to find the maximum number of times the target
string can be formed from the characters in the string s
. To do this, the code employs two Counter
objects from the collections
module to count the frequency of each character in s
and target
and then calculates the minimum number of times the target
can be formed by the available characters in s
.
Time Complexity
The time complexity of the code mainly comes from the following parts:
- The
Counter(s)
operation - Counting the frequency of each character ins
has a time complexity ofO(n)
, wheren
is the length ofs
. - The
Counter(target)
operation - Similarly, counting the frequency of each character intarget
has a time complexity ofO(m)
, wherem
is the length oftarget
. - The
min(cnt1[c] // v for c, v in cnt2.items())
operation - Iterating over each unique character intarget
and checking its availability ins
has a time complexity ofO(u)
, whereu
is the number of unique characters intarget
.
Since these operations are sequential and not nested, the overall time complexity is O(n + m + u)
. However, since u
cannot exceed the size of the alphabet used (let's say k
for a fixed-size alphabet), and m
will always be less than or equal to n
in the worst case when each character in s
is part of target
, we can simplify the time complexity to O(n)
as k
is a constant and can be considered negligible.
Space Complexity
The space complexity is determined by:
- The space required to store the
Counter
objects fors
andtarget
- This would beO(a + b)
, wherea
is the number of unique characters ins
andb
is the number of unique characters intarget
. - Since the size of the alphabet is fixed, and hence
a
andb
will be at mostk
(the alphabet size), we can consider the space complexity to beO(k)
.
Consequently, the overall space complexity of the code snippet is O(k)
which is effectively a constant space because the alphabet size does not change with input size.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
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!