title: Happy Number slug: happy-number difficulty: easy leetcode_id: 202 leetcode_url: https://leetcode.com/problems/happy-number/ categories: - math - hash-tables patterns: - slug: fast-slow-pointers is_optimal: true 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   **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   **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   **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.