3138. Minimum Length of Anagram Concatenation
Problem Description
You are given a string s
that is formed by concatenating multiple anagrams of some unknown string t
. Your task is to find the minimum possible length of string t
.
An anagram is a rearrangement of the letters of a string. For example, "aab", "aba", and "baa" are all anagrams of each other.
The key insight is that string s
is composed entirely of anagrams of t
placed one after another. For instance:
- If
t = "ab"
, thens
could be "abba" (concatenation of "ab" and "ba", both anagrams of "ab") - If
t = "abc"
, thens
could be "abcbcacab" (concatenation of "abc", "bca", and "cab")
Since s
is made up of complete anagrams of t
, the length of t
must be a divisor of the length of s
. The algorithm checks each possible divisor k
of the length of s
, starting from the smallest, to see if s
can be partitioned into segments of length k
where each segment is an anagram of the others.
The checking process works by:
- Counting the frequency of each character in the entire string
s
- For each potential length
k
, verifying that whens
is divided into segments of lengthk
, each segment contains the same characters with the same frequencies - This is validated by ensuring that the character count in any segment, when multiplied by the number of segments (
n/k
), equals the total character count ins
The function returns the smallest valid length k
that satisfies these conditions.
Intuition
The key observation is that if string s
is formed by concatenating anagrams of string t
, then s
must be divisible into equal-length segments where each segment is an anagram of t
. This means the length of t
must divide the length of s
evenly.
Let's think about what this means mathematically. If s
has length n
and is made up of anagrams of t
with length k
, then we must have n = m * k
for some integer m
(the number of anagrams concatenated). This tells us that k
must be a divisor of n
.
Now, how do we verify if a particular length k
is valid? If we split s
into segments of length k
, each segment should be an anagram of the others. Anagrams have the same character frequencies, so each segment should have identical character counts.
Here's the clever part: instead of comparing all segments pairwise, we can use a global property. If each segment has the same character frequencies, then the total count of any character c
in the entire string s
should equal the count of c
in one segment multiplied by the number of segments. In other words, if character c
appears x
times in one segment and there are n/k
segments, then c
should appear exactly x * (n/k)
times in the entire string s
.
Since we want the minimum possible length, we enumerate divisors of n
from smallest to largest. The first divisor that passes our validation check must be the answer, because any smaller valid length would have been found earlier in our enumeration.
This approach is efficient because we only need to count characters once for the entire string, and then for each potential length k
, we just need to verify the character counts in the segments match our expected pattern.
Solution Approach
The implementation follows a systematic enumeration and validation approach:
Step 1: Character Frequency Counting
First, we count the frequency of each character in the entire string s
using a Counter (hash table), storing it in cnt
. This gives us the total occurrence of each character in the string.
Step 2: Enumerate Possible Lengths
We iterate through all possible lengths i
from 1 to n
(where n = len(s)
). For each i
, we check if it's a divisor of n
by verifying n % i == 0
. Only divisors are valid candidates for the length of string t
.
Step 3: Validation Function check(k)
For each valid divisor k
, we validate whether s
can be split into anagrams of length k
:
- We iterate through
s
in chunks of sizek
usingrange(0, n, k)
- For each chunk starting at position
i
, we extract the substrings[i : i + k]
- We count the character frequencies in this chunk using
Counter(s[i : i + k])
, storing it incnt1
- We then verify that for every character
c
in the originalcnt
:- The equation
cnt1[c] * (n // k) == cnt[c]
must hold - This means: (count of
c
in one segment) × (number of segments) = (total count ofc
ins
)
- The equation
- If any character fails this check, we return
False
- If all chunks pass the validation, we return
True
Step 4: Return the Minimum Length
Since we enumerate lengths from smallest to largest, the first length i
that passes the validation check is immediately returned as the answer. This guarantees we find the minimum possible length.
Time Complexity Analysis:
- Counting characters in
s
:O(n)
- For each divisor
k
, thecheck
function examinesn/k
segments, each of lengthk
, resulting inO(n)
time - The number of divisors of
n
is at mostO(√n)
- Overall complexity:
O(n√n)
Space Complexity: O(1)
or O(26)
for the character counter, since we're dealing with lowercase English letters.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "abcabcbb"
.
Step 1: Count character frequencies in the entire string
cnt = {'a': 2, 'b': 4, 'c': 2}
- Length
n = 8
Step 2: Try each divisor of 8 from smallest to largest
Try length 1:
- Check if 8 % 1 == 0? Yes, it's a divisor
- Split into 8 segments: ['a'], ['b'], ['c'], ['a'], ['b'], ['c'], ['b'], ['b']
- Take first segment ['a']:
cnt1 = {'a': 1}
- Validate: Does
cnt1['a'] * 8 = 1 * 8 = 8
equalcnt['a'] = 2
? No! (8 ≠ 2) - Length 1 fails ❌
Try length 2:
- Check if 8 % 2 == 0? Yes, it's a divisor
- Split into 4 segments: ['ab'], ['ca'], ['bc'], ['bb']
- Take first segment ['ab']:
cnt1 = {'a': 1, 'b': 1}
- Validate:
- Does
cnt1['a'] * 4 = 1 * 4 = 4
equalcnt['a'] = 2
? No! (4 ≠ 2)
- Does
- Length 2 fails ❌
Try length 4:
- Check if 8 % 4 == 0? Yes, it's a divisor
- Split into 2 segments: ['abca'], ['bcbb']
- Take first segment ['abca']:
cnt1 = {'a': 2, 'b': 1, 'c': 1}
- Validate:
- Does
cnt1['a'] * 2 = 2 * 2 = 4
equalcnt['a'] = 2
? No! (4 ≠ 2)
- Does
- Length 4 fails ❌
Try length 8:
- Check if 8 % 8 == 0? Yes, it's a divisor
- Split into 1 segment: ['abcabcbb']
- Take first segment ['abcabcbb']:
cnt1 = {'a': 2, 'b': 4, 'c': 2}
- Validate:
- Does
cnt1['a'] * 1 = 2 * 1 = 2
equalcnt['a'] = 2
? Yes! ✓ - Does
cnt1['b'] * 1 = 4 * 1 = 4
equalcnt['b'] = 4
? Yes! ✓ - Does
cnt1['c'] * 1 = 2 * 1 = 2
equalcnt['c'] = 2
? Yes! ✓
- Does
- Length 8 passes! ✓
Answer: 8
In this case, the string cannot be formed by concatenating anagrams of any smaller string, so the minimum length is the entire string itself.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def minAnagramLength(self, s: str) -> int:
5 """
6 Find the minimum length of string t such that s can be formed by
7 concatenating anagrams of t.
8
9 Args:
10 s: Input string to analyze
11
12 Returns:
13 Minimum length of the base string t
14 """
15 def is_valid_length(substring_length: int) -> bool:
16 """
17 Check if the string can be divided into equal parts where each part
18 is an anagram of the same base string.
19
20 Args:
21 substring_length: Length of each potential substring
22
23 Returns:
24 True if valid division exists, False otherwise
25 """
26 # Check each substring of the given length
27 for start_idx in range(0, total_length, substring_length):
28 # Get character frequency for current substring
29 current_substring_freq = Counter(s[start_idx : start_idx + substring_length])
30
31 # Verify that each character appears proportionally
32 # across all substrings
33 for char, total_count in overall_char_freq.items():
34 expected_count_per_substring = current_substring_freq[char]
35 num_substrings = total_length // substring_length
36
37 # Check if total count matches expected distribution
38 if expected_count_per_substring * num_substrings != total_count:
39 return False
40
41 return True
42
43 # Count frequency of all characters in the string
44 overall_char_freq = Counter(s)
45 total_length = len(s)
46
47 # Try each possible substring length from smallest to largest
48 for candidate_length in range(1, total_length + 1):
49 # Only check lengths that evenly divide the total length
50 if total_length % candidate_length == 0:
51 if is_valid_length(candidate_length):
52 return candidate_length
53
1class Solution {
2 private int stringLength;
3 private char[] charArray;
4 private int[] globalCharFrequency = new int[26];
5
6 public int minAnagramLength(String s) {
7 // Initialize instance variables
8 stringLength = s.length();
9 this.charArray = s.toCharArray();
10
11 // Count frequency of each character in the entire string
12 for (int i = 0; i < stringLength; i++) {
13 globalCharFrequency[charArray[i] - 'a']++;
14 }
15
16 // Try each possible substring length starting from 1
17 for (int substringLength = 1; ; substringLength++) {
18 // Only check lengths that evenly divide the total string length
19 if (stringLength % substringLength == 0 && isValidSubstringLength(substringLength)) {
20 return substringLength;
21 }
22 }
23 }
24
25 private boolean isValidSubstringLength(int substringLength) {
26 // Check each substring of the given length
27 for (int startIndex = 0; startIndex < stringLength; startIndex += substringLength) {
28 int[] localCharFrequency = new int[26];
29
30 // Count character frequencies in current substring
31 for (int currentIndex = startIndex; currentIndex < startIndex + substringLength; currentIndex++) {
32 localCharFrequency[charArray[currentIndex] - 'a']++;
33 }
34
35 // Verify that each character appears proportionally
36 // Each substring should have the same character distribution
37 int numberOfSubstrings = stringLength / substringLength;
38 for (int charIndex = 0; charIndex < 26; charIndex++) {
39 // Total occurrences should equal local occurrences * number of substrings
40 if (localCharFrequency[charIndex] * numberOfSubstrings != globalCharFrequency[charIndex]) {
41 return false;
42 }
43 }
44 }
45 return true;
46 }
47}
48
1class Solution {
2public:
3 int minAnagramLength(string s) {
4 int n = s.size();
5
6 // Count frequency of each character in the entire string
7 int totalFreq[26] = {0};
8 for (char c : s) {
9 totalFreq[c - 'a']++;
10 }
11
12 // Lambda function to check if string can be divided into anagram substrings of length k
13 auto canDivideIntoAnagrams = [&](int substringLength) {
14 // Check each substring of length k
15 for (int startIdx = 0; startIdx < n; startIdx += substringLength) {
16 // Count frequency of characters in current substring
17 int substringFreq[26] = {0};
18 for (int j = startIdx; j < startIdx + substringLength; ++j) {
19 substringFreq[s[j] - 'a']++;
20 }
21
22 // Verify if current substring frequency scaled up matches total frequency
23 // Each character's frequency in substring * number of substrings should equal total frequency
24 int numSubstrings = n / substringLength;
25 for (int charIdx = 0; charIdx < 26; ++charIdx) {
26 if (substringFreq[charIdx] * numSubstrings != totalFreq[charIdx]) {
27 return false;
28 }
29 }
30 }
31 return true;
32 };
33
34 // Try each possible substring length starting from 1
35 // Return the first valid length (which will be the minimum)
36 for (int length = 1; ; ++length) {
37 // Only check lengths that evenly divide the string length
38 if (n % length == 0 && canDivideIntoAnagrams(length)) {
39 return length;
40 }
41 }
42 }
43};
44
1/**
2 * Finds the minimum length of an anagram that can be repeated to form the given string
3 * @param s - The input string to analyze
4 * @returns The minimum length of the repeating anagram pattern
5 */
6function minAnagramLength(s: string): number {
7 const stringLength: number = s.length;
8
9 // Count frequency of each character in the entire string
10 const characterFrequency: Record<string, number> = {};
11 for (let i = 0; i < stringLength; i++) {
12 const char: string = s[i];
13 characterFrequency[char] = (characterFrequency[char] || 0) + 1;
14 }
15
16 /**
17 * Checks if the string can be divided into segments of length k
18 * where each segment is an anagram of the others
19 * @param segmentLength - The length of each segment to test
20 * @returns True if all segments are anagrams of each other
21 */
22 const isValidSegmentLength = (segmentLength: number): boolean => {
23 const numberOfSegments: number = Math.floor(stringLength / segmentLength);
24
25 // Check each segment of length segmentLength
26 for (let startIndex = 0; startIndex < stringLength; startIndex += segmentLength) {
27 // Count character frequency in current segment
28 const segmentFrequency: Record<string, number> = {};
29 for (let j = startIndex; j < startIndex + segmentLength; j++) {
30 const char: string = s[j];
31 segmentFrequency[char] = (segmentFrequency[char] || 0) + 1;
32 }
33
34 // Verify that each character appears the correct number of times
35 // Each character's total count should equal its segment count * number of segments
36 for (const [character, totalCount] of Object.entries(characterFrequency)) {
37 const expectedSegmentCount: number = segmentFrequency[character] || 0;
38 if (expectedSegmentCount * numberOfSegments !== totalCount) {
39 return false;
40 }
41 }
42 }
43 return true;
44 };
45
46 // Try each possible segment length starting from 1
47 for (let candidateLength = 1; ; candidateLength++) {
48 // Only test lengths that evenly divide the string length
49 if (stringLength % candidateLength === 0 && isValidSegmentLength(candidateLength)) {
50 return candidateLength;
51 }
52 }
53}
54
Time and Space Complexity
Time Complexity: O(n × A)
, where n
is the length of string s
, and A
is the number of factors (divisors) of n
.
The analysis breaks down as follows:
- The outer loop iterates through all possible lengths from
1
ton
, but only performs the check function whenn % i == 0
, meaningi
is a divisor ofn
. The number of divisors ofn
isA
. - For each valid divisor
i
, thecheck
function is called:- The check function iterates through the string in chunks of size
i
, creatingn/i
segments - For the first segment, it creates a Counter which takes
O(i)
time - Then it iterates through all unique characters in the original Counter (at most
26
for lowercase letters), performing constant-time operations - Overall, the check function takes
O(i + |Σ|)
time, which simplifies toO(i)
sincei
can be up ton
- The check function iterates through the string in chunks of size
- Summing across all divisors:
Σ(i for all divisors i of n) ≤ n × A
- Creating the initial Counter takes
O(n)
time
Therefore, the total time complexity is O(n × A)
.
Space Complexity: O(|Σ|)
, where Σ
is the character set (lowercase letters in this case).
The space analysis:
- The Counter
cnt
stores at most26
character-frequency pairs for lowercase letters:O(|Σ|)
- The Counter
cnt1
inside the check function also stores at most26
entries:O(|Σ|)
- Other variables use constant space
The total space complexity is O(|Σ|)
, which equals O(26) = O(1)
for lowercase English letters.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Redundant Validation of All Segments
The Pitfall:
The current implementation checks every segment of length k
in the string, even though mathematically, if all segments are anagrams of each other, they must all have the same character distribution. This means we're doing unnecessary work by validating each segment individually.
Why It Happens:
The code iterates through all segments (for start_idx in range(0, total_length, substring_length)
) and validates the character count for each one. However, if the string is valid for a given length k
, all segments must be identical in character composition.
The Fix: Instead of checking all segments, we only need to:
- Check if the total character counts are divisible by the number of segments
- Optionally verify that the first segment's character distribution matches the expected pattern
Improved Solution:
from collections import Counter
class Solution:
def minAnagramLength(self, s: str) -> int:
def is_valid_length(substring_length: int) -> bool:
num_segments = total_length // substring_length
# Get the character frequency of the first segment as reference
first_segment_freq = Counter(s[:substring_length])
# Check if total counts are consistent with this pattern
for char, count in first_segment_freq.items():
if overall_char_freq[char] != count * num_segments:
return False
# Also check that no extra characters exist in the total count
for char in overall_char_freq:
if char not in first_segment_freq:
return False
return True
overall_char_freq = Counter(s)
total_length = len(s)
for candidate_length in range(1, total_length + 1):
if total_length % candidate_length == 0:
if is_valid_length(candidate_length):
return candidate_length
2. Not Handling Character Distribution Divisibility Early
The Pitfall: The solution doesn't pre-check whether the character frequencies are compatible with a given segment count before doing detailed validation.
Why It Matters: If we have 7 'a's in the string and we're checking if the string can be divided into 3 segments, it's impossible since 7 is not divisible by 3. We can eliminate such cases immediately.
Optimized Approach:
def is_valid_length(substring_length: int) -> bool:
num_segments = total_length // substring_length
# Early check: all character counts must be divisible by num_segments
for char, count in overall_char_freq.items():
if count % num_segments != 0:
return False
# Now verify the first segment matches expected pattern
first_segment_freq = Counter(s[:substring_length])
for char, count in first_segment_freq.items():
if overall_char_freq[char] != count * num_segments:
return False
return True
This optimization can significantly reduce computation time, especially for longer strings with many potential divisors.
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
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!