Happy Number (Magical Number)
Given an integer n, return whether or not it is a "magical" number. A magical number is an integer such that when you repeatedly replace the number with the sum of the squares of its digits, it eventually becomes one.
Note: If the process enters a cycle (repeats numbers) without reaching 1, the number is not magical.
Example(s)
Example 1:
Input: n = 19
Output: true
Explanation:
19 → 1² + 9² = 1 + 81 = 82
82 → 8² + 2² = 64 + 4 = 68
68 → 6² + 8² = 36 + 64 = 100
100 → 1² + 0² + 0² = 1 + 0 + 0 = 1 ✓
Since we reached 1, 19 is a magical number.
Example 2:
Input: n = 22
Output: false
Explanation:
22 → 2² + 2² = 4 + 4 = 8
8 → 8² = 64
64 → 6² + 4² = 36 + 16 = 52
52 → 5² + 2² = 25 + 4 = 29
29 → 2² + 9² = 4 + 81 = 85
85 → 8² + 5² = 64 + 25 = 89
89 → 8² + 9² = 64 + 81 = 145
145 → 1² + 4² + 5² = 1 + 16 + 25 = 42
42 → 4² + 2² = 16 + 4 = 20
20 → 2² + 0² = 4 + 0 = 4
4 → 4² = 16
16 → 1² + 6² = 1 + 36 = 37
37 → 3² + 7² = 9 + 49 = 58
58 → 5² + 8² = 25 + 64 = 89 (cycle detected!)
Since we entered a cycle without reaching 1, 22 is not magical.
Example 3:
Input: n = 1
Output: true
Explanation: Already 1, so it's magical.
Example 4:
Input: n = 7
Output: true
Explanation:
7 → 7² = 49
49 → 4² + 9² = 16 + 81 = 97
97 → 9² + 7² = 81 + 49 = 130
130 → 1² + 3² + 0² = 1 + 9 + 0 = 10
10 → 1² + 0² = 1 + 0 = 1 ✓
Solution
The solution uses a set to detect cycles:
- Calculate sum of squares: Helper function to compute sum of squares of digits
- Track seen numbers: Use a set to store numbers we've encountered
- Iterate: Keep calculating the next number until we reach 1 or detect a cycle
- Cycle detection: If we see a number we've seen before, we're in a cycle → return false
- Success: If we reach 1, return true
- JavaScript Solution
- Python Solution
JavaScript Solution - Set Approach
/**
* Check if a number is magical (happy number)
* @param {number} n - Integer to check
* @return {boolean} - True if n is magical
*/
function isHappy(n) {
const seen = new Set();
/**
* Calculate sum of squares of digits
* @param {number} num - Number to process
* @return {number} - Sum of squares of digits
*/
function getNext(num) {
let sum = 0;
while (num > 0) {
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
}
// Keep iterating until we reach 1 or detect a cycle
while (n !== 1 && !seen.has(n)) {
seen.add(n);
n = getNext(n);
}
return n === 1;
}
// Test cases
console.log('Example 1:', isHappy(19)); // true
console.log('Example 2:', isHappy(22)); // false
console.log('Example 3:', isHappy(1)); // true
console.log('Example 4:', isHappy(7)); // true
console.log('Test 5:', isHappy(2)); // falseOutput:
Python Solution - Set Approach
def is_happy(n: int) -> bool:
"""
Check if a number is magical (happy number)
Args:
n: Integer to check
Returns:
bool: True if n is magical
"""
seen = set()
def get_next(num: int) -> int:
"""
Calculate sum of squares of digits
Returns:
int: Sum of squares of digits
"""
total = 0
while num > 0:
digit = num % 10
total += digit * digit
num = num // 10
return total
# Keep iterating until we reach 1 or detect a cycle
while n != 1 and n not in seen:
seen.add(n)
n = get_next(n)
return n == 1
# Test cases
print('Example 1:', is_happy(19)) # True
print('Example 2:', is_happy(22)) # False
print('Example 3:', is_happy(1)) # True
print('Example 4:', is_happy(7)) # True
print('Test 5:', is_happy(2)) # FalseOutput:
Alternative Solution (Floyd's Cycle Detection)
Here's a solution using Floyd's cycle detection algorithm (two pointers) that uses O(1) space:
- JavaScript Floyd's Cycle
- Python Floyd's Cycle
/**
* Floyd's cycle detection - O(1) space
*/
function isHappyFloyd(n) {
function getNext(num) {
let sum = 0;
while (num > 0) {
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
}
// Two pointers: slow and fast
let slow = n;
let fast = getNext(n);
// Move slow one step, fast two steps
while (fast !== 1 && slow !== fast) {
slow = getNext(slow);
fast = getNext(getNext(fast));
}
return fast === 1;
}
def is_happy_floyd(n: int) -> bool:
"""
Floyd's cycle detection - O(1) space
"""
def get_next(num: int) -> int:
total = 0
while num > 0:
digit = num % 10
total += digit * digit
num = num // 10
return total
# Two pointers: slow and fast
slow = n
fast = get_next(n)
# Move slow one step, fast two steps
while fast != 1 and slow != fast:
slow = get_next(slow)
fast = get_next(get_next(fast))
return fast == 1
Alternative: String-Based Digit Extraction
Here's an alternative that converts to string to extract digits:
- JavaScript String-Based
- Python String-Based
/**
* Alternative: String-based digit extraction
*/
function isHappyString(n) {
const seen = new Set();
function getNext(num) {
return String(num)
.split('')
.reduce((sum, digit) => sum + digit * digit, 0);
}
while (n !== 1 && !seen.has(n)) {
seen.add(n);
n = getNext(n);
}
return n === 1;
}
def is_happy_string(n: int) -> bool:
"""
Alternative: String-based digit extraction
"""
seen = set()
def get_next(num: int) -> int:
return sum(int(d) ** 2 for d in str(num))
while n != 1 and n not in seen:
seen.add(n)
n = get_next(n)
return n == 1
Complexity
- Time Complexity: O(log n) - The number of digits in n is O(log n), and we process each digit. The cycle detection adds some overhead, but in practice it's efficient.
- Space Complexity:
- Set approach: O(log n) - We store numbers in the set. The maximum number we can reach is bounded.
- Floyd's approach: O(1) - Only uses two pointers, no extra space.
Approach
The solution uses cycle detection:
-
Calculate next number: Sum of squares of digits
- Extract each digit using modulo and division
- Square each digit and sum them
-
Detect cycles: Two approaches
- Set approach: Track all seen numbers in a set
- Floyd's cycle detection: Use two pointers (slow and fast)
-
Termination conditions:
- Success: If we reach 1, return true
- Cycle: If we see a number we've seen before, return false
Key Insights
- Cycle detection is crucial: Without it, we'd loop infinitely
- All cycles contain 4: Interestingly, all unhappy numbers eventually reach 4
- Bounded values: The sum of squares of digits is always less than the original number (for numbers > 81)
- Floyd's algorithm: More space-efficient but slightly more complex
- Digit extraction: Can use modulo/division or string conversion
Step-by-Step Example
Let's trace through Example 1: n = 19
Initial: n = 19, seen = {}
Step 1: n = 19 (not 1, not in seen)
seen.add(19) → seen = {19}
getNext(19):
digits: 1, 9
sum = 1² + 9² = 1 + 81 = 82
n = 82
Step 2: n = 82 (not 1, not in seen)
seen.add(82) → seen = {19, 82}
getNext(82):
digits: 8, 2
sum = 8² + 2² = 64 + 4 = 68
n = 68
Step 3: n = 68 (not 1, not in seen)
seen.add(68) → seen = {19, 82, 68}
getNext(68):
digits: 6, 8
sum = 6² + 8² = 36 + 64 = 100
n = 100
Step 4: n = 100 (not 1, not in seen)
seen.add(100) → seen = {19, 82, 68, 100}
getNext(100):
digits: 1, 0, 0
sum = 1² + 0² + 0² = 1 + 0 + 0 = 1
n = 1
Step 5: n = 1
Return true (n === 1)
Mathematical Insight
Interesting fact: All unhappy numbers eventually enter a cycle that includes 4:
- 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 (cycle)
So we could optimize by checking if we reach 4:
while (n !== 1 && n !== 4) {
n = getNext(n);
}
return n === 1;
However, this is less general and the set approach is clearer.
Edge Cases
- n = 1: Already magical, return true immediately
- n = 0: Not magical (0 → 0, cycle)
- Single digit: Works correctly
- Large numbers: Eventually reduces to smaller numbers
- Negative numbers: Typically not considered (but could handle by taking absolute value)
Optimization: Hardcoded Cycle Detection
Since we know all cycles contain 4, we can optimize:
- JavaScript Optimized
- Python Optimized
/**
* Optimized: Check for 4 instead of using a set
*/
function isHappyOptimized(n) {
function getNext(num) {
let sum = 0;
while (num > 0) {
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
}
// All unhappy numbers eventually reach 4
while (n !== 1 && n !== 4) {
n = getNext(n);
}
return n === 1;
}
def is_happy_optimized(n: int) -> bool:
"""
Optimized: Check for 4 instead of using a set
"""
def get_next(num: int) -> int:
total = 0
while num > 0:
digit = num % 10
total += digit * digit
num = num // 10
return total
# All unhappy numbers eventually reach 4
while n != 1 and n != 4:
n = get_next(n)
return n == 1
Related Problems
- Add Digits: Repeatedly add digits until single digit
- Ugly Number: Check if number has only certain prime factors
- Perfect Number: Check if number equals sum of its divisors
- Self Dividing Numbers: Check if number is divisible by all its digits
Takeaways
- Cycle detection is essential for this problem
- Set approach is simple and clear
- Floyd's algorithm saves space (O(1) vs O(log n))
- All cycles contain 4 - useful optimization
- Digit extraction can be done with modulo/division or string conversion
- Mathematical insight helps optimize the solution