title: Bulb Switcher
slug: bulb-switcher
difficulty: medium
leetcode_id: 319
leetcode_url: https://leetcode.com/problems/bulb-switcher/
categories:
  - math
patterns:
  - slug: greedy
    is_optimal: true

function_signature: "def bulb_switch(n: int) -> int:"

test_cases:
  visible:
    - input: { n: 3 }
      expected: 1
    - input: { n: 0 }
      expected: 0
    - input: { n: 1 }
      expected: 1
  hidden:
    - input: { n: 4 }
      expected: 2
    - input: { n: 9 }
      expected: 3
    - input: { n: 16 }
      expected: 4
    - input: { n: 25 }
      expected: 5
    - input: { n: 100 }
      expected: 10
    - input: { n: 99999999 }
      expected: 9999

description: |
  There are `n` bulbs that are initially off. You first turn on all the bulbs, then you turn off every second bulb.

  On the third round, you toggle every third bulb (turning on if it's off or turning off if it's on). For the i<sup>th</sup> round, you toggle every i<sup>th</sup> bulb. For the n<sup>th</sup> round, you only toggle the last bulb.

  Return *the number of bulbs that are on after `n` rounds*.

constraints: |
  - `0 <= n <= 10^9`

examples:
  - input: "n = 3"
    output: "1"
    explanation: "At first, the three bulbs are [off, off, off]. After round 1: [on, on, on]. After round 2: [on, off, on]. After round 3: [on, off, off]. Only bulb 1 remains on."
  - input: "n = 0"
    output: "0"
    explanation: "There are no bulbs, so zero bulbs are on."
  - input: "n = 1"
    output: "1"
    explanation: "There is one bulb. After round 1, it is turned on and stays on."

explanation:
  intuition: |
    Imagine each bulb as a light switch that gets flipped every time its position number is divisible by the current round number.

    Consider bulb `k`. It gets toggled in round `i` if and only if `i` divides `k` evenly. For example, bulb 6 gets toggled in rounds 1, 2, 3, and 6 — exactly four times (the divisors of 6). Since it starts OFF and is toggled an **even** number of times, it ends up OFF.

    Now consider bulb 9. Its divisors are 1, 3, and 9 — that's **three** toggles (odd). Starting OFF and toggling an odd number of times leaves it ON.

    Here's the key insight: **most numbers have divisors that come in pairs**. If `d` divides `n`, then so does `n/d`. For example, with 12: (1, 12), (2, 6), (3, 4) — three pairs, six divisors.

    But **perfect squares are special**. For 9: the pair (3, 3) is really just one divisor counted once, giving us an odd total. Only perfect squares have an odd number of divisors, so only bulbs at positions 1, 4, 9, 16, 25, ... remain ON.

    The answer is simply: how many perfect squares are there from 1 to `n`? That's `floor(sqrt(n))`.

  approach: |
    We solve this using a **Mathematical Insight** approach:

    **Step 1: Understand the toggle pattern**

    - Bulb `k` is toggled once for each divisor of `k`
    - A bulb ends up ON if toggled an odd number of times
    - Only perfect squares have an odd number of divisors

    &nbsp;

    **Step 2: Count perfect squares**

    - Perfect squares from 1 to `n` are: 1, 4, 9, 16, ..., up to the largest ≤ `n`
    - The count of perfect squares ≤ `n` is `floor(sqrt(n))`

    &nbsp;

    **Step 3: Return the result**

    - Return `int(sqrt(n))` which gives us the count of bulbs that remain ON

    &nbsp;

    This mathematical reduction transforms an apparently complex simulation into a single operation, making it O(1) in time.

  common_pitfalls:
    - title: The Simulation Trap
      description: |
        A naive approach is to simulate all `n` rounds, toggling bulbs in an array:
        - Create an array of `n` bulbs
        - For each round `i` from 1 to `n`, toggle every i<sup>th</sup> bulb
        - Count bulbs that are ON

        This is **O(n²) time** and **O(n) space**. With `n` up to `10^9`, this would require billions of operations and gigabytes of memory — completely infeasible.

        Even an optimised simulation counting toggles per bulb is O(n × d) where d is average divisors, still too slow.
      wrong_approach: "Simulating n rounds with array manipulation"
      correct_approach: "Mathematical formula: floor(sqrt(n))"

    - title: Missing the Divisor Insight
      description: |
        Without recognising the divisor pattern, you might try various optimisations that still don't achieve O(1):
        - Counting divisors for each bulb individually
        - Looking for patterns by computing small cases

        The breakthrough comes from understanding WHY perfect squares behave differently: their square root is paired with itself, creating an odd divisor count.
      wrong_approach: "Counting divisors for each position"
      correct_approach: "Recognise only perfect squares have odd divisors"

    - title: Integer Overflow in Square Root
      description: |
        With `n` up to `10^9`, you need to ensure your square root calculation handles large integers correctly.

        In Python, `int(n ** 0.5)` works well, but be aware that floating-point precision can occasionally cause off-by-one errors for very large perfect squares. Using `math.isqrt(n)` (Python 3.8+) provides exact integer square root.
      wrong_approach: "Assuming float sqrt is always exact"
      correct_approach: "Use math.isqrt() for guaranteed integer precision"

  key_takeaways:
    - "**Divisor counting**: The number of times a position is toggled equals its divisor count"
    - "**Perfect square property**: Only perfect squares have an odd number of divisors, because the square root pairs with itself"
    - "**Mathematical reduction**: Recognising patterns can reduce O(n²) simulations to O(1) formulas"
    - "**Brainteaser technique**: When simulation is infeasible, look for mathematical invariants in the problem structure"

  time_complexity: "O(1). Computing the integer square root is a constant-time operation."
  space_complexity: "O(1). We only store the result, no additional data structures needed."

solutions:
  - approach_name: Mathematical Formula
    is_optimal: true
    code: |
      import math

      def bulb_switch(n: int) -> int:
          # Only perfect squares have odd number of divisors
          # Count of perfect squares from 1 to n is floor(sqrt(n))
          return math.isqrt(n)
    explanation: |
      **Time Complexity:** O(1) — Single square root computation.

      **Space Complexity:** O(1) — No additional memory used.

      The key insight is that bulb `k` ends up ON only if it has an odd number of divisors. Since divisors come in pairs (d, n/d) except when d = n/d (i.e., n is a perfect square), only perfect square positions remain ON. We count perfect squares ≤ n with `floor(sqrt(n))`.

  - approach_name: Simulation (Brute Force)
    is_optimal: false
    code: |
      def bulb_switch(n: int) -> int:
          if n == 0:
              return 0

          # Create array of bulbs (False = off, True = on)
          bulbs = [False] * (n + 1)  # 1-indexed

          # Simulate each round
          for round_num in range(1, n + 1):
              # Toggle every round_num-th bulb
              for bulb in range(round_num, n + 1, round_num):
                  bulbs[bulb] = not bulbs[bulb]

          # Count bulbs that are on
          return sum(bulbs)
    explanation: |
      **Time Complexity:** O(n²) — For each of n rounds, we toggle up to n/i bulbs, summing to O(n log n) toggles, but the outer loop dominates.

      **Space Complexity:** O(n) — Array to store bulb states.

      This approach directly simulates the problem but is far too slow for large inputs. With n up to 10^9, this would require billions of operations. Included to illustrate why the mathematical approach is essential.
