2287. Rearrange Characters to Make Target String

EasyHash TableStringCounting
Leetcode Link

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:

  1. Count the occurrences of each character in s. This gives us the supply for each character available in s.
  2. Count the occurrences of each character in target. This represents the demand for each character to formulate a target string.
  3. 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 in s by the demand of that character in target.
  4. 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 complete target 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:

1class Solution:
2    def rearrangeCharacters(self, s: str, target: str) -> int:
3        cnt1 = Counter(s)         # Count characters in s
4        cnt2 = Counter(target)    # Count characters in target
5        # Calculate min number of times we can form target character-wise
6        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.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

What is the space complexity of the following code?

1int sum(int n) {
2  if (n <= 0) {
3    return 0;
4  }
5  return n + sum(n - 1);
6}

Solution Approach

The solution implements a straightforward approach using two algorithms/data structures:

  • Counters (Hash Tables): The Python Counter class from the collections 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 in s and once for target. 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 of target that can be formed.

Here's a breakdown of the implementation steps:

  1. cnt1 = Counter(s): We count each character in s. Counter iterates over s, and for each character, it increments the character's count in the hash table. For example, if s = "bbaac", then cnt1 will be { 'b': 2, 'a': 2, 'c': 1 }.

  2. cnt2 = Counter(target): Similarly, we count the characters in target. If target = "abc", then cnt2 will be { 'a': 1, 'b': 1, 'c': 1 }.

  3. The generator expression (cnt1[c] // v for c, v in cnt2.items()) iterates over the items of cnt2, which are tuples of (character, count) from target. For each character c in target, it calculates how many times that character can be taken from s. This is the supply we have (from cnt1[c]) divided by the demand (from v).

  4. min(...): We wrap the generator expression with min to find the character that is the limiting factor in forming the target. This step ensures that we consider the "least abundant" character in terms of how many complete target strings we can formulate. The minimum value is the maximum number of target strings we can form.

Here's the code section again for reference:

1class Solution:
2    def rearrangeCharacters(self, s: str, target: str) -> int:
3        cnt1 = Counter(s)         # Count characters in s
4        cnt2 = Counter(target)    # Count characters in target
5        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.

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

Consider the classic dynamic programming of longest increasing subsequence:

Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.

For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is [3, 7, 40, 80].

What is the recurrence relation?

Example 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 in cnt1 = {'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 in cnt2 = {'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 is cnt2['a'] = 1, so 2 // 1 = 2. We can form two 'a's.
  • For character 'b': the supply is cnt1['b'] = 3 and the demand is cnt2['b'] = 1, so 3 // 1 = 3. We can form three 'b's.
  • For character 'c': the supply is cnt1['c'] = 2 and the demand is cnt2['c'] = 1, so 2 // 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 the target string abc twice using characters from s.

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
Not Sure What to Study? Take the 2-min Quiz:

Which of the following is equvalent to O(3*2^n + n^3 + n!+ log n)?

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:

  1. The Counter(s) operation - Counting the frequency of each character in s has a time complexity of O(n), where n is the length of s.
  2. The Counter(target) operation - Similarly, counting the frequency of each character in target has a time complexity of O(m), where m is the length of target.
  3. The min(cnt1[c] // v for c, v in cnt2.items()) operation - Iterating over each unique character in target and checking its availability in s has a time complexity of O(u), where u is the number of unique characters in target.

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:

  1. The space required to store the Counter objects for s and target - This would be O(a + b), where a is the number of unique characters in s and b is the number of unique characters in target.
  2. Since the size of the alphabet is fixed, and hence a and b will be at most k (the alphabet size), we can consider the space complexity to be O(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.

Fast Track Your Learning with Our Quick Skills Quiz:

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫