title: Happy Number
slug: happy-number
difficulty: easy
leetcode_id: 202
leetcode_url: https://leetcode.com/problems/happy-number/
categories:
  - math
  - hash-tables
patterns:
  - fast-slow-pointers

function_signature: "def is_happy(n: int) -> bool:"

test_cases:
  visible:
    - input: { n: 19 }
      expected: true
    - input: { n: 2 }
      expected: false
    - input: { n: 1 }
      expected: true
  hidden:
    - input: { n: 7 }
      expected: true
    - input: { n: 4 }
      expected: false
    - input: { n: 100 }
      expected: true

description: |
  Write an algorithm to determine if a number `n` is happy.

  A **happy number** is a number defined by the following process:

  - Starting with any positive integer, replace the number by the sum of the squares of its digits.
  - Repeat the process until the number equals `1` (where it will stay), or it **loops endlessly in a cycle** which does not include `1`.
  - Those numbers for which this process **ends in 1** are happy.

  Return `true` if `n` is a happy number, and `false` if not.

constraints: |
  - `1 <= n <= 2^31 - 1`

examples:
  - input: "n = 19"
    output: "true"
    explanation: "1^2 + 9^2 = 82 -> 8^2 + 2^2 = 68 -> 6^2 + 8^2 = 100 -> 1^2 + 0^2 + 0^2 = 1"
  - input: "n = 2"
    output: "false"
    explanation: "The sequence 2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 enters a cycle that never reaches 1."

explanation:
  intuition: |
    Think of this problem as following a path through a maze of numbers. Starting from `n`, you compute the sum of squared digits to get the next number, then repeat. The key insight is that this sequence must eventually do one of two things: either reach `1` (happy!) or enter a cycle (unhappy).

    Why must it cycle? Because the sum of squared digits for any number has an upper bound. For a number with `d` digits, the maximum sum is `d * 81` (when all digits are `9`). For the largest input (`2^31 - 1`, which has 10 digits), the maximum possible sum is 810. So after at most one step, you're working with numbers in a bounded range, and a bounded sequence that never terminates must eventually repeat.

    This cycle-detection insight opens up two elegant solutions:

    1. **Hash Set**: Track every number you've seen. If you see a repeat before reaching `1`, there's a cycle.
    2. **Floyd's Cycle Detection (Fast-Slow Pointers)**: Use two "runners" through the sequence at different speeds. If there's a cycle, the fast runner will eventually lap the slow runner.

    The fast-slow pointer approach is particularly elegant because it uses O(1) space instead of O(n) for storing visited numbers.

  approach: |
    We solve this using **Floyd's Cycle Detection** (also known as the tortoise and hare algorithm):

    **Step 1: Define a helper function**

    - `get_next(n)`: Computes the sum of squares of digits
    - Extract each digit using modulo and integer division
    - Square each digit and accumulate the sum

    &nbsp;

    **Step 2: Initialise two pointers**

    - `slow`: Starts at `n`, moves one step at a time
    - `fast`: Starts at `get_next(n)`, moves two steps at a time

    &nbsp;

    **Step 3: Run the cycle detection loop**

    - While `fast != 1` and `fast != slow`:
      - Move `slow` one step: `slow = get_next(slow)`
      - Move `fast` two steps: `fast = get_next(get_next(fast))`
    - If they meet before reaching `1`, there's a cycle (unhappy)
    - If `fast` reaches `1`, the number is happy

    &nbsp;

    **Step 4: Return the result**

    - Return `fast == 1`
    - If `fast` is `1`, we found happiness; otherwise we detected a cycle

  common_pitfalls:
    - title: Infinite Loop Without Cycle Detection
      description: |
        A naive approach might just keep computing the next number forever:

        ```python
        while n != 1:
            n = sum_of_squares(n)
        return True
        ```

        This will never terminate for unhappy numbers like `2`, which cycle endlessly through `2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 -> ...`

        You **must** detect cycles, either with a hash set or Floyd's algorithm.
      wrong_approach: "Loop until n equals 1"
      correct_approach: "Track visited numbers or use Floyd's cycle detection"

    - title: Forgetting Edge Cases
      description: |
        The number `1` is already happy (sum of squares of `1` is `1`). Single-digit numbers like `7` are also happy (`7 -> 49 -> 97 -> 130 -> 10 -> 1`).

        Make sure your initial setup handles these correctly. With Floyd's algorithm, initialising `slow = n` and `fast = get_next(n)` naturally handles `n = 1` because `fast` immediately becomes `1`.

    - title: Integer Overflow in get_next
      description: |
        When extracting digits, some implementations might use string conversion which is slower. The mathematical approach using `n % 10` and `n // 10` is both faster and avoids any potential issues with very large numbers during intermediate steps.

        However, since the sum of squared digits is bounded (maximum ~810 for 10-digit numbers), overflow is not a concern for the result.

  key_takeaways:
    - "**Cycle detection pattern**: Floyd's algorithm (fast-slow pointers) is useful whenever you need to detect cycles in a sequence with O(1) space"
    - "**Bounded sequences**: Recognising that the sequence values are bounded (max ~810) proves that cycles must occur for non-happy numbers"
    - "**Math vs Hash Table tradeoff**: The hash set approach is simpler to understand but uses O(k) space where k is the cycle length; Floyd's uses O(1)"
    - "**Related problems**: This pattern applies to Linked List Cycle, Find the Duplicate Number, and other sequence-based cycle problems"

  time_complexity: "O(log n). The number of digits in n is O(log n), and we process each number in the sequence. The sequence length is bounded by a constant for any starting value."
  space_complexity: "O(1) for Floyd's algorithm, or O(log n) for the hash set approach (storing visited numbers)."

solutions:
  - approach_name: Floyd's Cycle Detection
    is_optimal: true
    code: |
      def is_happy(n: int) -> bool:
          def get_next(num: int) -> int:
              """Calculate sum of squares of digits."""
              total = 0
              while num > 0:
                  digit = num % 10  # Extract last digit
                  total += digit * digit  # Add its square
                  num //= 10  # Remove last digit
              return total

          # Floyd's algorithm: slow moves 1 step, fast moves 2 steps
          slow = n
          fast = get_next(n)

          # Continue until fast reaches 1 or they meet (cycle detected)
          while fast != 1 and slow != fast:
              slow = get_next(slow)  # One step
              fast = get_next(get_next(fast))  # Two steps

          # Happy if we reached 1, unhappy if cycle detected
          return fast == 1
    explanation: |
      **Time Complexity:** O(log n) — Each number has O(log n) digits to process, and the sequence is bounded.

      **Space Complexity:** O(1) — Only uses two pointer variables regardless of input size.

      Floyd's cycle detection elegantly solves the problem: if a cycle exists, the fast pointer will eventually catch up to the slow pointer. If no cycle exists (happy number), fast reaches 1 first.

  - approach_name: Hash Set
    is_optimal: false
    code: |
      def is_happy(n: int) -> bool:
          def get_next(num: int) -> int:
              """Calculate sum of squares of digits."""
              total = 0
              while num > 0:
                  digit = num % 10
                  total += digit * digit
                  num //= 10
              return total

          # Track all numbers we've seen
          seen = set()

          while n != 1 and n not in seen:
              seen.add(n)  # Mark current number as visited
              n = get_next(n)  # Move to next in sequence

          # Happy if we reached 1, unhappy if we saw a repeat
          return n == 1
    explanation: |
      **Time Complexity:** O(log n) — Same as Floyd's approach.

      **Space Complexity:** O(log n) — Stores visited numbers in the set.

      This approach is more intuitive: just remember what you've seen. If you see a number twice before reaching 1, you're in a cycle. The tradeoff is using extra memory for the set.
