Facebook Pixel

3138. Minimum Length of Anagram Concatenation

MediumHash TableStringCounting
Leetcode Link

Problem Description

You are given a string s, which is known to be a concatenation of anagrams of some string t. Your task is to return the minimum possible length of the string t. An anagram is formed by rearranging the letters of a string. For example, "aab", "aba", and "baa" are anagrams of "aab".

Intuition

To solve this problem, note that the length of string t must be a factor of the length of string s, because the entire string s is constructed by repeating anagrams of string t. We aim to find the smallest possible k which is a divisor of the length n of s, such that s can be divided into chunks of size k where each chunk is an anagram of t.

  1. Count Characters: First, we count the occurrences of each character in the string s and store these counts.

  2. Check Factors: Traverse potential lengths k which are factors of n, starting from the smallest.

  3. Verify Chunks: For each k, check if the entire string s can be subdivided into substrings of length k, each having the character distribution necessary to form an anagram of t. This means each chunk of length k should have the same character frequency such that when multiplied by the number of such chunks (n/k), it matches the frequency in s.

Whenever k satisfies the condition, it implies the smallest possible k and hence the minimum possible length of t.

Solution Approach

The problem is approached using enumeration to find the smallest possible length of t, which is a factor of the length of s. Here's how the solution works:

  1. Character Counting: First, a character frequency map cnt for the entire string s is created using the Counter from the collections module. This map keeps track of how many times each character appears in s.

  2. Iterate Over Possible Lengths: Iterate over each possible length k from 1 to n (n being the length of s). Only consider k if it is a divisor of n. This ensures we are checking only those lengths which can evenly divide s into parts.

  3. Verification Function: Define a helper function check(k) to determine if k can be a valid length for t. In this function:

    • Sub-Chunks Analysis: Iterate through the string s in segments of length k. For each segment, calculate the frequency of characters in that segment using a local Counter.
    • Frequency Comparison: Compare each character's frequency across the full string s (in cnt) against the frequency expected from the current segment (cnt1). Specifically, verify that each character frequency in cnt1 multiplied by n/k (the number of such segments) matches the corresponding frequency in cnt.
    • If all comparisons hold true for the current k, check(k) returns True.
  4. Return the Minimum Length: As soon as a valid k is found such that check(k) returns True, return k as it represents the smallest possible length of t.

This approach efficiently finds the minimum possible length of t by leveraging divisor properties and frequency validation over potential substring partitions of s.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a small example using the solution approach described.

Assume the input string s is "abcabc". Our goal is to find the minimum possible length of a string t such that s is a concatenation of anagrams of t.

  1. Character Counting: First, determine the character frequency for s. Here, s = "abcabc", so the frequency map is:

    • 'a': 2
    • 'b': 2
    • 'c': 2
  2. Iterate Over Possible Lengths: Next, iterate over each potential length for t. Given that s has length 6, the divisors are 1, 2, 3, and 6.

  3. Check Each Length:

    • k = 1: Can't form valid anagrams as single-character chunks can't rearrange to yield the original.
    • k = 2: Divide s into chunks "ab", "ca", "bc". Check if each can be a rearrangement of a 2-character t. The character frequencies won't match across the full string, failing this check.
    • k = 3: Divide s into chunks "abc", "abc". Each segment matches directly, perfectly forming the original string by rearrangement. Check if the character frequencies match: for each chunk, the local Counter would be {'a': 1, 'b': 1, 'c': 1}, and scaling this by n/k (which is 2) matches the full count in s.
  4. Return the Minimum Length: For k = 3, the check is successful, indicating t could be "abc". This implies the minimum length of t is 3. Thus, it satisfies the condition, and 3 is returned as the result.

This illustrates how the solution efficiently finds the minimum possible length of t by examining divisor factors and their corresponding substring segmentations in s.

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def minAnagramLength(self, s: str) -> int:
5        # Helper function to determine if the string can be divided into 
6        # anagrams of length `k`
7        def check(k: int) -> bool:
8            # Iterate over the string in chunks of size `k`
9            for i in range(0, n, k):
10                # Count the frequency of characters in each chunk
11                current_chunk_count = Counter(s[i : i + k])
12                # Check if count of each character multiplied by number of chunks equals the total count
13                for char, total_count in char_count.items():
14                    if current_chunk_count[char] * (n // k) != total_count:
15                        return False
16            return True
17
18        char_count = Counter(s)  # Count of all characters in the string
19        n = len(s)  # Length of the input string
20
21        # Check for each possible length `i` from 1 to `n` if the string can
22        # be divided evenly into segments of `i` that are anagrams
23        for i in range(1, n + 1):
24            # Proceed only if `n` is divisible by `i` and the chunks are valid anagrams
25            if n % i == 0 and check(i):
26                return i
27
1class Solution {
2    private int n; // Length of the input string
3    private char[] s; // Array of characters from the input string
4    private int[] cnt = new int[26]; // Frequency array for each letter in the alphabet
5
6    public int minAnagramLength(String s) {
7        n = s.length();
8        this.s = s.toCharArray();
9      
10        // Populate the cnt array with the frequency of each character in the input string
11        for (int i = 0; i < n; ++i) {
12            ++cnt[this.s[i] - 'a'];
13        }
14
15        // Iterate over possible lengths of subsequences
16        for (int i = 1; ; ++i) {
17            // Check if the current length is a divisor of n and if it can form an anagram
18            if (n % i == 0 && check(i)) {
19                return i; // Return the smallest divisor length that can form an anagram
20            }
21        }
22    }
23
24    private boolean check(int k) {
25        // Validate if the input string can be divided into blocks of size 'k' that are anagrams
26
27        for (int i = 0; i < n; i += k) {
28            int[] cnt1 = new int[26]; // Temporary frequency array for current block
29          
30            // Count the frequency of each character in the current block of size 'k'
31            for (int j = i; j < i + k; ++j) {
32                ++cnt1[s[j] - 'a'];
33            }
34          
35            // Compare the frequency of characters in the current block with the global frequency
36            for (int j = 0; j < 26; ++j) {
37                if (cnt1[j] * (n / k) != cnt[j]) {
38                    return false; // Return false if the frequencies do not match
39                }
40            }
41        }
42        return true; // Return true if all blocks are valid
43    }
44}
45
1class Solution {
2public:
3    int minAnagramLength(std::string s) {
4        int n = s.size();
5
6        // Count frequency of each character in the string
7        int charCount[26] = {};
8        for (char c : s) {
9            charCount[c - 'a']++;
10        }
11
12        // Lambda function to check if a division of length 'k' can form an anagram
13        auto check = [&](int k) {
14            // Traverse each segment of length 'k'
15            for (int i = 0; i < n; i += k) {
16                int segmentCount[26] = {};
17                for (int j = i; j < i + k; ++j) {
18                    segmentCount[s[j] - 'a']++;
19                }
20                // Verify if the segment's distribution matches with total string
21                for (int j = 0; j < 26; ++j) {
22                    if (segmentCount[j] * (n / k) != charCount[j]) {
23                        return false;
24                    }
25                }
26            }
27            return true;
28        };
29
30        // Iterate over possible lengths to find the minimum valid length
31        for (int i = 1;; ++i) {
32            if (n % i == 0 && check(i)) {
33                return i;
34            }
35        }
36    }
37};
38
1function minAnagramLength(s: string): number {
2    const n = s.length;
3    const count: Record<string, number> = {}; // Store the frequency of each character in the string
4
5    // Build a frequency count of the characters in the string
6    for (let i = 0; i < n; i++) {
7        count[s[i]] = (count[s[i]] || 0) + 1;
8    }
9
10    // Helper function to check if a given length k can divide the string into anagrams
11    const check = (k: number): boolean => {
12        for (let i = 0; i < n; i += k) {
13            const currentCount: Record<string, number> = {}; // Frequency of characters in the current segment
14
15            // Compute frequency for the current segment of length k
16            for (let j = i; j < i + k; j++) {
17                currentCount[s[j]] = (currentCount[s[j]] || 0) + 1;
18            }
19
20            // Verify if current segment can be part of the anagram structure
21            for (const [char, value] of Object.entries(count)) {
22                if (currentCount[char] * ((n / k) | 0) !== value) {
23                    return false; // If there's a mismatch, it's not valid
24                }
25            }
26        }
27        return true; // All segments are valid
28    };
29
30    // Start checking from the smallest possible segment length that divides n
31    for (let i = 1; ; ++i) {
32        if (n % i === 0 && check(i)) { // If n is divisible by i and it forms valid anagrams
33            return i; // Return the minimum length found
34        }
35    }
36}
37

Time and Space Complexity

The time complexity of the provided code is O(n \times A), where n is the length of the string s, and A is the number of divisors (factors) of n. This is because for each divisor i of n, the code checks if partitioning the string into n / i parts of length i can create a valid anagram structure. The space complexity is O(|\Sigma|), where \Sigma represents the character set, typically the set of lowercase letters, since it uses a Counter to tally the occurrences of characters in the string.

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What's the relationship between a tree and a graph?


Recommended Readings

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


Load More