A clear explanation of constructing the magical string by using the string itself as run-length instructions.
Problem Restatement
We are given an integer n.
There is a special infinite string s made only of characters '1' and '2'.
The first part of the string is:
1221121221221121122...This string is called magical because if we group equal consecutive characters, the group lengths form the same string again.
For example:
s = 1 22 11 2 1 22 1 22 11 2 11 22 ...
length = 1 2 2 1 1 2 1 2 2 1 2 2 ...The length sequence is again:
122112122122...We need to return how many '1' characters appear in the first n characters of s.
The constraints are 1 <= n <= 10^5. The examples include n = 6, where the first six characters are "122112" and contain three '1' characters.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer n |
| Output | Count of '1' characters in the first n characters |
| String alphabet | Only '1' and '2' |
| Constraint | 1 <= n <= 100000 |
Function shape:
def magicalString(n: int) -> int:
...Examples
Example 1:
n = 6The first six characters are:
122112There are three '1' characters.
Answer:
3Example 2:
n = 1The first character is:
1Answer:
1First Thought: Build the String Directly
The string describes its own run lengths.
We already know the beginning:
122This means:
| Group | Character | Length |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
Now the next length comes from the next unread character in the string.
Starting with:
s = 122
^
read pointerThe read pointer is at index 2, where s[2] = 2.
The previous group used character 2, so the next group must use character 1.
Since the count is 2, append two 1s:
s = 12211Move the read pointer forward.
Now the next count is s[3] = 1.
The next character alternates to 2.
Append one 2:
s = 122112This process continues until the string has at least n characters.
Key Insight
Use the current contents of s as instructions for how to extend s.
Maintain:
| Variable | Meaning |
|---|---|
s | The generated magical string as a list of integers |
i | Reads the next group length from s |
next_num | The value to append next, either 1 or 2 |
At every step:
count = s[i]Then append next_num exactly count times.
After that, flip next_num:
next_num = 3 - next_numThis works because the only possible values are 1 and 2.
Current next_num | 3 - next_num |
|---|---|
| 1 | 2 |
| 2 | 1 |
Algorithm
Start with:
s = [1, 2, 2]
i = 2
next_num = 1Then repeat while len(s) < n:
- Read the next count from
s[i]. - Append
next_numthat many times. - Flip
next_num. - Move
iforward.
After the string reaches length n, count how many 1s appear in s[:n].
Correctness
The magical string is defined by its run lengths.
The initial string [1, 2, 2] gives the first two groups:
1
22The pointer i = 2 points to the length of the third group.
At each step, s[i] is the length of the next group to append. Since groups alternate between 1 and 2, next_num is always the correct group value.
The algorithm appends exactly s[i] copies of next_num, so the new group has the required length.
Then it advances to the next run-length instruction and flips the appended digit for the following group.
By induction, after every iteration, the generated prefix agrees with the magical string. Once the generated prefix has length at least n, the first n characters are correct. Counting the 1s in that prefix gives the required answer.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We generate at most slightly more than n elements |
| Space | O(n) | We store the generated prefix |
The loop may append one or two values each step, but the total number of appended values is proportional to n.
Implementation
class Solution:
def magicalString(self, n: int) -> int:
s = [1, 2, 2]
i = 2
next_num = 1
while len(s) < n:
count = s[i]
for _ in range(count):
s.append(next_num)
next_num = 3 - next_num
i += 1
return s[:n].count(1)Code Explanation
We seed the known prefix:
s = [1, 2, 2]The read pointer starts at index 2:
i = 2That value tells us the length of the third group.
The third group should be made of 1s:
next_num = 1Inside the loop, this reads the next group length:
count = s[i]Then we append the next digit count times:
for _ in range(count):
s.append(next_num)After creating a group of 1s, the next group must be 2s. After creating a group of 2s, the next group must be 1s.
This flip is done by:
next_num = 3 - next_numFinally, we move to the next instruction:
i += 1Return the number of 1s in the first n generated characters:
return s[:n].count(1)Slightly Optimized Implementation
We can count 1s while building the string.
This avoids scanning the prefix again at the end.
class Solution:
def magicalString(self, n: int) -> int:
if n <= 3:
return 1
s = [1, 2, 2]
ones = 1
i = 2
next_num = 1
while len(s) < n:
count = s[i]
for _ in range(count):
if len(s) == n:
break
s.append(next_num)
if next_num == 1:
ones += 1
next_num = 3 - next_num
i += 1
return onesTesting
def run_tests():
s = Solution()
assert s.magicalString(1) == 1
assert s.magicalString(2) == 1
assert s.magicalString(3) == 1
assert s.magicalString(4) == 2
assert s.magicalString(5) == 3
assert s.magicalString(6) == 3
assert s.magicalString(10) == 5
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
n = 1 | Smallest valid input |
n = 2 | Prefix "12" has one 1 |
n = 3 | Prefix "122" has one 1 |
n = 4 | Prefix "1221" has two 1s |
n = 5 | Prefix "12211" has three 1s |
n = 6 | Official example |
n = 10 | Checks several generated groups |