Core Problem Description
Given a range of numbers, find all numbers within that range that are formed by concatenating multiple identical digit segments. For Part 1, we only need to find numbers formed by exactly two identical segments. For Part 2, we need to find numbers formed by any number of identical segments (at least two).
| Number | Meets Part 1 Requirement | Meets Part 2 Requirement | Explanation |
|---|---|---|---|
| 123123 | Formed by two “123” segments | ||
| 121212 | Formed by three “12” segments | ||
| 1234 | No repeated segment | ||
| 1111 | Can be seen as two “11” or four “1” segments |
Thought Process
String-based Approach
The problem itself is not difficult. The simplest approach is to use string operations to determine if a number is composed of repeated segments. Specifically:
- Convert the number to a string
s. - Iterate over possible segment lengths
k, from1tos.len() / 2. - For each segment length, check if the string length is divisible by
k. - If divisible, extract the first segment
part = &s[0..k], and check ifsequalspartrepeated the appropriate number of times. - If a matching segment is found, the number is valid.
While this method is simple and intuitive, its time complexity is high, especially when dealing with large number ranges.
Mathematical Approach
Starting with Part 1’s requirement, consider how to mathematically determine if a number is formed by two identical segments.
Take the number 123123 as an example. We can express it as:
We can observe that for any even-length number, if it is formed by two identical -digit segments, then it can be written in the form , where is the length of . Let’s denote as . The problem then reduces to checking if a number is divisible by .
Based on this analysis, we can design the following algorithm:
- For an even-length number , calculate its length .
- Compute , and then .
- Check if is divisible by .
- If the condition is met, is valid.
This method has lower time complexity, as we only need one division operation per number. However, we still need to iterate over all numbers in the given range.
Reverse Thinking - Generation Method
Since we need to find all valid numbers, let’s think in reverse: directly generate these numbers instead of checking each number individually. For Part 1, we can generate all valid numbers with the following steps:
- For a specific total digit length , compute .
- Compute .
- Iterate over all possible segment values . If falls within the given range, add it to the result set.
Caution is needed: must be constrained to ensure it is a -digit number, preventing the generation of invalid numbers.
For example, when using as the , cannot be used as (the generated would be invalid); similarly, is also invalid.
We need to restrict to the range , which exactly fills digits.
Going further, since the final requirement is the sum of all valid numbers, we can directly calculate this sum using the Gaussian summation formula without explicitly generating each number. The algorithm thus becomes:
- For the given number range and each possible even total digit length , compute , , and .
- Calculate the starting and ending values for : and .
- Use the Gaussian summation formula to calculate the sum of values, then multiply by to get the final sum.
This way, the time complexity is dramatically reduced. We no longer need to iterate over each number but can directly compute the result. The complexity drops from approximately to (nearly linear with the number of digits, ).
Generalizing to Any Number of Segments
Building on the foundation from Part 1, we can attempt to generalize to Part 2, finding numbers formed by any number (at least two) of identical segments. For example, the number 121212 can be expressed as:
We can see that this essentially requires modifying the previously fixed . For a number formed by identical segments, each of length , we can define as:
Thus, a valid number can be expressed as:
Based on this, we can design the following algorithm:
- For the given number range and each possible total digit length , find all possible values for (where must be a divisor of and ).
- For each , compute and .
- Calculate the starting and ending values for : and , where , .
- Use the Gaussian summation formula to calculate the sum of values, then multiply by to get the partial sum for this pair.
But is this correct? Let’s think. Consider the number :
Similarly, for :
A problem arises: the same number would be counted multiple times, leading to an incorrect sum. We need to ensure each number is counted only once.
Which numbers are counted multiple times? Precisely those numbers that can be formed using shorter segments. For example, can be seen as formed from segment length , so it gets counted again for segment lengths and . Similarly, is counted for both segment lengths and .
We can observe that if two segmentation and for the same total length satisfy being a divisor of , then the segmentation with the longer segment length will include numbers generated by the segmentation with the shorter segment length. More formally: for a given total digit count , the segmentation corresponding to a divisor will include all numbers generated by segmentation corresponding to divisors of (where is also a divisor of ).
This leads to a core deduplication idea: for each , only consider segmentation where the repeat count (where ) is a prime number, then sum all results. Because any repeating pattern with a composite repeat count can be decomposed into smaller repeats with prime counts, and thus would already be included in the counts for prime . Algorithmically, we can break this down into two steps:
- Find all prime factors of , denoted . Calculate the results corresponding to using each as the repeat count.
- Use the inclusion-exclusion principle to remove overlapping counts. Since prime numbers are co-prime, the only possible overlap is the pattern where the segment length is (i.e., all digits identical). Therefore, we only need to subtract the contribution from the segment length pattern from the total sum.
This approach not only ensures correctness but also maintains high computational efficiency.
Core Algorithm Implementation
type Range = (u64, u64);
struct Puzzle { ranges: Vec<Range>,}
impl Puzzle {
fn prime_factors(mut n: u32) -> Vec<u32> { let mut factors = BTreeSet::new(); while n.is_multiple_of(2) { factors.insert(2); n /= 2; } let mut divisor = 3; while divisor * divisor <= n { while n.is_multiple_of(divisor) { factors.insert(divisor); n /= divisor; } divisor += 2; } if n > 1 { factors.insert(n); }
factors.into_iter().collect() }
fn get_sum_invalid_ids(range: Range, n: u32, repeat: u32) -> u64 { // The pattern repeats every k = n / repeat digits let k = n / repeat; // Calculate the lower and upper bounds for n-digit numbers with the given // pattern let upper = 10u64.pow(n) - 1; let base = upper / (10u64.pow(k) - 1); let lower = 10u64.pow(k - 1) * base; // Get the overlap between the given range and (lower, upper) let (start, end) = range; let start = start.max(lower); let end = end.min(upper); // Convert back to the base range let start = start.div_ceil(base); let end = end / base; if start > end { return 0; }
(end - start + 1) * (start + end) / 2 * base }}
impl Solution for Puzzle { fn part1(&self) -> String { self.ranges .par_iter() .map(|&(start, end)| { let min_n = start.ilog10() + 1; let max_n = end.ilog10() + 1;
(min_n..=max_n) .filter(|n| n % 2 == 0) .map(|n| Self::get_sum_invalid_ids((start, end), n, 2)) .sum::<u64>() }) .sum::<u64>() .to_string() }
fn part2(&self) -> String { self.ranges .par_iter() .map(|&(start, end)| { let min_n = start.ilog10() + 1; let max_n = end.ilog10() + 1;
(min_n..=max_n) .filter(|&n| n > 1) .map(|n| { // Sum of all repeating digits (e.g., 1111, 2222, ..., 9999 for n=4) let all_same = Self::get_sum_invalid_ids((start, end), n, n); // Get all patterns with smaller, prime repeat factors Self::prime_factors(n).into_iter().filter(|&k| k < n).fold( all_same, |mut sum, k| { sum += Self::get_sum_invalid_ids((start, end), n, k); sum -= all_same; sum }, ) }) .sum::<u64>() }) .sum::<u64>() .to_string() }}Summary
The complete code can be found here.
Through in-depth analysis of the problem and the application of mathematical methods, we successfully transformed the task from one requiring iteration over a large number of candidates into an efficient computation problem. By leveraging the structural properties of numbers and the inclusion-exclusion principle, we not only improved algorithmic efficiency but also avoided complex conditional logic. Although the problem itself is not overly complex, the optimization process proved to be quite interesting.