LeetCode 481 - Magical String
This problem defines a special infinite sequence called the magical string. The string contains only the characters '1' and '2', and its defining property is very unusual: If you group consecutive identical digits together and record the length of each group, the resulting…
Difficulty: 🟡 Medium
Topics: Two Pointers, String
Solution
Problem Understanding
This problem defines a special infinite sequence called the magical string. The string contains only the characters '1' and '2', and its defining property is very unusual:
If you group consecutive identical digits together and record the length of each group, the resulting sequence of lengths recreates the original string itself.
For example, the beginning of the magical string is:
1221121221221121122...
If we split it into groups of consecutive equal digits:
1 | 22 | 11 | 2 | 1 | 22 | 1 | 22 | ...
The lengths of these groups are:
1 2 2 1 1 2 1 2 ...
That sequence is exactly the same as the magical string.
The input is an integer n, and we must determine how many '1' characters appear in the first n elements of the magical string.
The constraints are:
1 <= n <= 10^5
This tells us several important things:
- The solution must be efficient enough to handle up to one hundred thousand characters.
- Generating the sequence incrementally is feasible.
- Expensive recomputation or repeated string rebuilding should be avoided.
There are also several edge cases that matter:
n = 1, where the answer is immediately1.- Very small values like
n = 2orn = 3, where initialization mistakes are common. - Cases where generation slightly exceeds
n, because the sequence is produced in groups rather than one character at a time. - Counting only the first
ncharacters, even if more characters are generated internally.
Approaches
Brute Force Approach
A naive solution would attempt to repeatedly rebuild the magical string by scanning the entire sequence every time we append new characters.
The idea would be:
- Start with the initial magical string prefix.
- Repeatedly group consecutive characters.
- Compute group lengths.
- Verify or regenerate the sequence from those lengths.
- Continue until the string reaches length
n.
This approach works conceptually because it directly follows the definition of the magical string. However, it is highly inefficient because grouping and rescanning the sequence repeatedly introduces unnecessary repeated work.
If we rebuild or scan the sequence many times, the total complexity can become quadratic.
With n = 10^5, an O(n^2) approach is too slow.
Optimal Approach
The key observation is that the magical string describes its own run lengths.
This means we can generate the sequence incrementally while simultaneously reading instructions from the sequence itself.
We maintain:
- A pointer that reads the next group size.
- A current digit to append, alternating between
1and2. - A dynamically growing array representing the magical string.
For example:
Initial sequence: 1 2 2
The next unread value is 2, meaning:
append two copies of the opposite digit
Since the last digit is 2, we append:
1 1
Now the sequence becomes:
1 2 2 1 1
The process continues until we generate at least n elements.
This works efficiently because every character is generated exactly once.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(n) | Repeated rescanning and regrouping |
| Optimal | O(n) | O(n) | Generates each character exactly once |
Algorithm Walkthrough
- Handle the smallest edge case immediately.
If n <= 3, we already know the magical string starts as:
122
The counts of '1' are easy to determine directly.
2. Initialize the magical string.
We begin with:
magical = [1, 2, 2]
This is the known starting prefix of the sequence. 3. Maintain a pointer for reading group sizes.
We use an index called head.
This pointer tells us how many copies of the next digit to append.
Initially:
head = 2
because the first unread run length is the third element. 4. Track the next digit to append.
We alternate between 1 and 2.
Since the current sequence ends with 2, the next appended digit will be 1.
5. Generate the sequence incrementally.
While the sequence length is less than n:
- Read the count from
magical[head] - Append the current digit that many times
- Count appended
1s if they fall within the firstnpositions - Flip the digit between
1and2 - Advance
head
- Stop once enough elements are generated.
Since we only care about the first n elements, generation stops as soon as the sequence reaches length n.
Why it works
The algorithm works because the magical string is self descriptive. Each value in the sequence specifies the size of the next group. By reading run lengths from the already generated portion while appending alternating digits, we exactly reproduce the magical string definition.
The invariant is:
Every generated prefix satisfies the magical string property.
Because each step extends the sequence using the next valid run length, correctness is preserved throughout the construction.
Python Solution
class Solution:
def magicalString(self, n: int) -> int:
if n <= 3:
return 1
magical = [1, 2, 2]
head = 2
next_num = 1
ones_count = 1
while len(magical) < n:
repeat = magical[head]
for _ in range(repeat):
magical.append(next_num)
if next_num == 1 and len(magical) <= n:
ones_count += 1
next_num = 2 if next_num == 1 else 1
head += 1
return ones_count
The implementation starts with the known prefix [1, 2, 2], which already satisfies the magical string property.
The variable head acts as the reader pointer. Instead of recomputing group sizes, we directly use values already present in the sequence.
The variable next_num tracks which digit should be appended next. Since groups alternate between 1 and 2, we simply toggle the value after each group.
Whenever a 1 is appended within the first n positions, we increment ones_count.
The loop stops once the sequence contains at least n elements.
Go Solution
func magicalString(n int) int {
if n <= 3 {
return 1
}
magical := []int{1, 2, 2}
head := 2
nextNum := 1
onesCount := 1
for len(magical) < n {
repeat := magical[head]
for i := 0; i < repeat; i++ {
magical = append(magical, nextNum)
if nextNum == 1 && len(magical) <= n {
onesCount++
}
}
if nextNum == 1 {
nextNum = 2
} else {
nextNum = 1
}
head++
}
return onesCount
}
The Go implementation follows the same logic as the Python solution.
Slices are used instead of Python lists, and append dynamically grows the sequence. Integer overflow is not a concern because the constraint is only 10^5.
Unlike Python, Go does not have a built in ternary operator, so digit toggling uses a standard if statement.
Worked Examples
Example 1
Input:
n = 6
Initial state:
| Variable | Value |
|---|---|
| magical | [1, 2, 2] |
| head | 2 |
| next_num | 1 |
| ones_count | 1 |
Step by step generation:
| Step | repeat | Appended | magical | ones_count |
|---|---|---|---|---|
| Initial | - | - | [1,2,2] | 1 |
| 1 | 2 | 1,1 | [1,2,2,1,1] | 3 |
| 2 | 1 | 2 | [1,2,2,1,1,2] | 3 |
Now length is 6, so we stop.
Result:
3
Example 2
Input:
n = 1
Since n <= 3, we immediately return:
1
The first character of the magical string is:
1
which contains exactly one '1'.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each character is generated exactly once |
| Space | O(n) | The generated magical string is stored |
The algorithm is linear because every appended digit is processed only once. The head pointer advances steadily through the sequence without revisiting earlier elements repeatedly.
The space complexity is also linear because we explicitly store the generated sequence up to size n.
Test Cases
solution = Solution()
assert solution.magicalString(1) == 1 # smallest input
assert solution.magicalString(2) == 1 # prefix "12"
assert solution.magicalString(3) == 1 # prefix "122"
assert solution.magicalString(4) == 2 # prefix "1221"
assert solution.magicalString(5) == 3 # prefix "12211"
assert solution.magicalString(6) == 3 # provided example
assert solution.magicalString(7) == 4 # prefix "1221121"
assert solution.magicalString(10) == 5 # larger prefix validation
assert solution.magicalString(20) == 10 # medium sized case
assert solution.magicalString(1000) > 0 # stress test
assert solution.magicalString(100000) > 0 # maximum constraint
Test Summary
| Test | Why |
|---|---|
n = 1 |
Smallest valid input |
n = 2 |
Small prefix handling |
n = 3 |
Initialization boundary |
n = 4 |
First generated extension |
n = 5 |
Multiple appended ones |
n = 6 |
Provided example |
n = 7 |
Alternating group validation |
n = 10 |
Longer sequence correctness |
n = 20 |
Medium sized verification |
n = 1000 |
Performance sanity check |
n = 100000 |
Maximum constraint stress test |
Edge Cases
One important edge case is n <= 3. The magical string starts as "122", and many implementations incorrectly attempt to generate additional values even for these tiny inputs. This can lead to off by one errors or incorrect counts. The implementation handles this immediately with a direct return of 1.
Another subtle case occurs when generation exceeds n. Since digits are appended in groups of size one or two, the algorithm may append slightly beyond the required prefix length. If counting is done carelessly, extra '1' values beyond position n could be included. The implementation avoids this by checking:
len(magical) <= n
before incrementing the count.
A third important edge case involves alternating digits correctly. Because the magical string is generated from its own run lengths, forgetting to alternate between 1 and 2 after each group breaks the entire construction. The implementation explicitly flips the value after processing every group, ensuring the generated sequence maintains the magical property.