questions F-L

This commit is contained in:
2025-05-25 11:47:04 +01:00
parent 798e0ba1df
commit 5dbe52df0d
54 changed files with 11235 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
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.