Skip to content

Commit 7975429

Browse files
committed
Add problem 3138: Minimum Length of Anagram Concatenation
1 parent fcd5222 commit 7975429

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,7 @@ pub mod problem_3131_find_the_integer_added_to_array_i;
21022102
pub mod problem_3133_minimum_array_end;
21032103
pub mod problem_3136_valid_word;
21042104
pub mod problem_3137_minimum_number_of_operations_to_make_word_k_periodic;
2105+
pub mod problem_3138_minimum_length_of_anagram_concatenation;
21052106
pub mod problem_3350_adjacent_increasing_subarrays_detection_ii;
21062107

21072108
#[cfg(test)]
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
pub struct Solution;
2+
3+
// ------------------------------------------------------ snip ------------------------------------------------------ //
4+
5+
use std::collections::HashMap;
6+
use std::collections::hash_map::Entry;
7+
use std::num::NonZero;
8+
9+
impl Solution {
10+
fn gcd(mut x: u32, mut y: u32) -> u32 {
11+
while y != 0 {
12+
(x, y) = (y, x % y);
13+
}
14+
15+
x
16+
}
17+
18+
fn get_factors(value: NonZero<u32>) -> Vec<NonZero<u32>> {
19+
let sqrt_value = value.isqrt();
20+
21+
let mut result = (2..sqrt_value.get())
22+
.filter_map(NonZero::new)
23+
.filter(|&x| value.get() % x == 0)
24+
.collect::<Vec<_>>();
25+
26+
let n = result.len();
27+
28+
if sqrt_value.get() * sqrt_value.get() == value.get() {
29+
result.push(sqrt_value);
30+
}
31+
32+
result.reserve(n + 1);
33+
34+
for i in (0..n).rev() {
35+
result.extend(result.get(i).copied().and_then(|x| NonZero::new(value.get() / x)));
36+
}
37+
38+
result.push(value);
39+
40+
result
41+
}
42+
43+
pub fn min_anagram_length(s: String) -> i32 {
44+
let mut counts = HashMap::<_, u32>::new();
45+
46+
s.bytes().for_each(|chunk| match counts.entry(chunk) {
47+
Entry::Occupied(occupied_entry) => *occupied_entry.into_mut() += 1,
48+
Entry::Vacant(vacant_entry) => {
49+
vacant_entry.insert(1);
50+
}
51+
});
52+
53+
let gcd = counts.values().copied().fold(0, Self::gcd);
54+
55+
let mut counts = [0_u32; 26];
56+
57+
let prefix_sums = s
58+
.bytes()
59+
.map(|c| {
60+
counts[usize::from(c) - usize::from(b'a')] += 1;
61+
62+
counts
63+
})
64+
.collect::<Box<_>>();
65+
66+
let gcd = NonZero::new(gcd).unwrap();
67+
let gcd_factors = Self::get_factors(gcd);
68+
69+
for &repeats in gcd_factors.iter().rev() {
70+
let chunk_size = s.len() / repeats.get() as usize;
71+
72+
if (chunk_size - 1..)
73+
.step_by(chunk_size)
74+
.map_while(|i| {
75+
prefix_sums.get(i).map(|right| {
76+
let left = prefix_sums
77+
.get(i.wrapping_sub(chunk_size))
78+
.unwrap_or(const { &[0; 26] });
79+
80+
(left, right)
81+
})
82+
})
83+
.all(|(left, right)| {
84+
right
85+
.iter()
86+
.zip(left)
87+
.zip(&counts)
88+
.all(|((right, left), &total)| (right - left) * repeats.get() == total)
89+
})
90+
{
91+
return chunk_size as _;
92+
}
93+
}
94+
95+
s.len() as _
96+
}
97+
}
98+
99+
// ------------------------------------------------------ snip ------------------------------------------------------ //
100+
101+
impl super::Solution for Solution {
102+
fn min_anagram_length(s: String) -> i32 {
103+
Self::min_anagram_length(s)
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod tests {
109+
#[test]
110+
fn test_solution() {
111+
super::super::tests::run::<super::Solution>();
112+
}
113+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
pub mod brute_force;
2+
3+
pub trait Solution {
4+
fn min_anagram_length(s: String) -> i32;
5+
}
6+
7+
#[cfg(test)]
8+
mod tests {
9+
use super::Solution;
10+
11+
pub fn run<S: Solution>() {
12+
let test_cases = [("abba", 2), ("cdef", 4), ("abcbcacabbaccba", 3), ("aabb", 4)];
13+
14+
for (s, expected) in test_cases {
15+
assert_eq!(S::min_anagram_length(s.to_string()), expected);
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)