Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

481. Magical String - Leetcode Solution

Problem Description

The Magical String problem asks you to generate a special string called the "magical string" up to a given length n, and count how many '1's are present in its first n characters.

The magical string S is a sequence of '1's and '2's with the following properties:

  • The first few elements of S are: "1221121221221121122..."
  • The sequence is constructed such that the counts of consecutive identical numbers (runs) themselves form the sequence S.
  • For example, the first run is "1" (one '1'), next run is "22" (two '2's), next run is "11" (two '1's), next run is "2" (one '2'), and so on.
  • Given an integer n, you must return the number of '1's in the first n characters of the magical string.
Constraints:
  • 1 <= n <= 10^5

Thought Process

At first, the problem may seem confusing because the sequence defines itself in terms of its own structure. A brute-force approach might be to try and write out the sequence by hand, but this quickly becomes infeasible for large n.

The key insight is that the sequence is built by reading itself: each number in the sequence tells you how many times to repeat the next digit (which alternates between '1' and '2'). Instead of generating the entire sequence as a string, we can use a list or array and simulate the process step by step, keeping track of how many '1's we add as we go.

We want to avoid unnecessary string operations and focus on efficiently building the sequence up to n characters, counting '1's as we go.

Solution Approach

We can break down the algorithm as follows:

  1. Initialize the sequence:
    • Start with the magical string as a list: [1, 2, 2].
    • Set a pointer (let's call it head) to the current position in the sequence that tells us how many times to append the next number.
    • Keep track of the next number to append (start with 1, since after 2 comes 1).
    • Initialize a counter for the number of '1's seen so far.
  2. Build the sequence:
    • While the length of the sequence is less than n:
      • Read the number at position head (this tells you how many times to append the next number).
      • Append the next number that many times.
      • If the next number is '1', increase the '1's counter by the number of times appended (but don't exceed n).
    • After appending, switch the next number (from 1 to 2, or 2 to 1).
    • Move head forward by 1.
  3. Return the result:
    • If the sequence length exceeds n, only count the '1's in the first n elements.

We use a list to efficiently append new numbers and a variable to keep track of the number of '1's, ensuring we don't waste memory or time.

Example Walkthrough

Let's walk through the process for n = 6:

  • Start with sequence: [1, 2, 2]
  • head = 2 (value is 2), next_num = 1
  • Append two '1's: [1, 2, 2, 1, 1] (total 1's so far: 3)
  • Switch next_num to 2, head = 3 (value is 1)
  • Append one '2': [1, 2, 2, 1, 1, 2]
  • Now the sequence has 6 elements: [1, 2, 2, 1, 1, 2]
  • Count '1's in first 6: positions 0, 3, 4 are '1' ⇒ 3 ones

Thus, the answer for n = 6 is 3.

Time and Space Complexity

  • Brute-force approach:
    • Writing out the sequence by hand or using inefficient string concatenation would take at least O(n2) time due to repeated copying.
    • Space is O(n) for storing the sequence.
  • Optimized approach (the above algorithm):
    • Each element is generated and appended exactly once, so time complexity is O(n).
    • Space complexity is O(n) for storing the sequence up to length n.
    • Counting '1's is done during generation, so no extra pass is needed.

Summary

The Magical String problem is a great example of a self-referential sequence that can be efficiently simulated using a list and a pointer. By understanding how the sequence "reads itself" to generate new elements, we can avoid brute-force methods and build the solution in linear time. The key insight is to use the sequence's own values to determine how many times to append the next number, all while counting the number of '1's as we go. This results in an elegant, efficient solution suitable for large inputs.

Code Implementation

class Solution:
    def magicalString(self, n: int) -> int:
        if n == 0:
            return 0
        s = [1, 2, 2]
        head = 2
        num = 1
        count = 1  # s[0] is '1'
        while len(s) < n:
            for _ in range(s[head]):
                s.append(num)
                if num == 1 and len(s) <= n:
                    count += 1
            num = 3 - num  # toggle between 1 and 2
            head += 1
        # If overshot, subtract any extra ones
        return sum(1 for i in s[:n] if i == 1)
      
class Solution {
public:
    int magicalString(int n) {
        if (n == 0) return 0;
        vector<int> s = {1, 2, 2};
        int head = 2, num = 1, count = 1;
        while (s.size() < n) {
            for (int i = 0; i < s[head]; ++i) {
                s.push_back(num);
            }
            num = 3 - num;
            ++head;
        }
        int result = 0;
        for (int i = 0; i < n; ++i) {
            if (s[i] == 1) ++result;
        }
        return result;
    }
};
      
class Solution {
    public int magicalString(int n) {
        if (n == 0) return 0;
        List<Integer> s = new ArrayList<>(Arrays.asList(1,2,2));
        int head = 2, num = 1;
        while (s.size() < n) {
            for (int i = 0; i < s.get(head); ++i) {
                s.add(num);
            }
            num = 3 - num;
            head++;
        }
        int count = 0;
        for (int i = 0; i < n; ++i) {
            if (s.get(i) == 1) count++;
        }
        return count;
    }
}
      
var magicalString = function(n) {
    if (n === 0) return 0;
    let s = [1, 2, 2];
    let head = 2, num = 1;
    while (s.length < n) {
        for (let i = 0; i < s[head]; ++i) {
            s.push(num);
        }
        num = 3 - num;
        head++;
    }
    let count = 0;
    for (let i = 0; i < n; ++i) {
        if (s[i] === 1) count++;
    }
    return count;
};