202. Happy Number
Learn various approaches to solving LeetCode's Happy Number problem, from using hash sets to Floyd's cycle detection algorithm. Discover efficient ways to determine if a number will eventually reach 1 through digit square sums or fall into an endless cycle.
Problem Definition
LeetCode Link: 202. Happy Number
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.
Example 1:
Input: n = 19
Output: true
Explanation:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
Example 2:
Input: n = 2
Output: false
Constraints:
1 <= n <= 231 - 1
Thought Process
At first glance, the problem may look purely mathematical, but it’s fundamentally about detecting cycles in a sequence. Once you start calculating the sum of the squares of digits repeatedly, there are two possible outcomes:
- You eventually reach
1
, confirming the number is happy. - You enter a loop without reaching
1
, confirming the number is unhappy.
To detect loops, we can leverage data structures like a hash set (or dictionary-like behavior), arrays, or even a two-pointer technique. Storing previously seen values allows us to identify when we’ve fallen into a cycle.
Solution 1 - Using a Dictionary (or Set)
class SolutionUsingSet:
def isHappy(self, n: int) -> bool:
record = set()
while True:
n = self.get_sum(n)
if n == 1:
return True
if n in record:
return False
record.add(n)
def get_sum(self, n: int) -> int:
new_num = 0
while n:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
How It Works
- Initialize a set to record numbers we've computed.
- Calculate the sum of the squares of the current number’s digits.
- If the result is
1
, the number is happy. - If the result is already in the set, a cycle is detected—return
false
. - Otherwise, keep adding results to the set and continue until one of the above conditions occurs.
Code Explanation
get_sum(n)
calculates the sum of the squares ofn
's digits.divmod(a, b)
is a Python function that returns a tuple of two numbers: the quotient and remainder whena
is divided byb
.- In
isHappy
, we loop until we find1
or a repeated sum. - If repeated, return
false
; if1
, returntrue
.
Complexity Analysis
- Time Complexity: O(log n) on average, as the sequence of sums decreases or cycles quickly.
- Space Complexity: O(log n) due to storing intermediate values in the set.
Solution 2 - Using Arrays as Hash Maps
class SolutionUsingArray:
def isHappy(self, n: int) -> bool:
record = []
while n not in record:
record.append(n)
new_num = 0
for i in str(n):
new_num += int(i)**2
if new_num == 1:
return True
else:
n = new_num
return False
How It Works
This approach is conceptually similar to using a set, but we store seen values in a list. While not as efficient for lookups, the problem’s nature ensures that cycles emerge quickly and remain small.
- Keep track of encountered sums in a list.
- If you reach
1
, returntrue
. - If a new sum is already in the list, return
false
.
Code Explanation
- Convert the number to a string to iterate over digits easily.
- Compute the sum of squares.
- Check if it’s
1
, or if it already exists in the list.
Complexity Analysis
- Time Complexity: O(k), where
k
is the length of the cycle or steps to reach1
. List lookups are O(k), butk
is small. - Space Complexity: Similar to using a set, O(k).
Solution 3 - A More Pythonic Set-Based Approach
How It Works
This solution is simply a cleaner, more concise version of the set-based approach. It uses Python’s expressive syntax to compute the sum of squares in a single line.
- While
n
is not1
, computen
as the sum of the squares of its digits. - If we detect a repeat, return
false
. - If we hit
1
, returntrue
.
class SolutionUsingSetSimplified:
def isHappy(self, n: int) -> bool:
seen = set()
while n != 1:
n = sum(int(i)**2 for i in str(n))
if n in seen:
return False
seen.add(n)
return True
Code Explanation
- We use a generator expression
sum(int(i)**2 for i in str(n))
to calculate the sum quickly. - A set
seen
tracks encountered numbers.
Complexity Analysis
- Time Complexity: O(log n) as before.
- Space Complexity: O(log n).
Additional Approach - Fast and Slow Pointer (Floyd’s Cycle Detection)
class SolutionUsingFastSlowPointer:
def isHappy(self, n: int) -> bool:
slow = n
fast = n
while True:
slow = self.get_sum(slow)
fast = self.get_sum(self.get_sum(fast))
if slow == 1 or fast == 1:
return True
if slow == fast:
return False
def get_sum(self, n: int) -> int:
new_num = 0
while n > 0:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
How It Works
Instead of relying on extra storage, we can detect cycles using a classic two-pointer technique (often used in linked list cycle detection):
- Define a function
get_sum(n)
to get the sum of the squares of the digits. - Maintain two values:
slow
andfast
.slow
moves through the sequence one step at a time.fast
moves two steps at a time.
- If
fast
orfast
’s next move ever equals1
, we found a happy number. - If
slow
andfast
pointers meet (equal each other) before finding1
, it means there is a cycle—returnfalse
.
This technique doesn’t need a set or list, thus saving space.
Code Explanation
- Move
slow
by callingget_sum
once per iteration. - Move
fast
by callingget_sum
twice per iteration. - If they meet, cycle detected → not happy.
- If
fast
(or a double iteration offast
) hits1
, we’re done → happy.
Complexity Analysis
- Time Complexity: O(log n), as before, since the sequence either collapses to
1
or quickly forms a cycle. - Space Complexity: O(1), no additional data structures required.
Key Takeaways
- Cycle Detection is the Core Concept:
The crux of solving this problem is realizing that if a number is not happy, it will fall into a cycle of repeated values. Detecting that cycle is critical. - Multiple Approaches, Same Goal:
All solutions—whether using sets, arrays, or the fast-slow pointer method—aim to detect loops in the sequence of transformed numbers. - Data Structure Selection:
Using a set is the most straightforward approach, offering quick lookups and easy detection of repeats. The fast and slow pointer approach is an elegant way to detect cycles without extra space. - Efficiency:
Although the complexity can vary slightly, all methods run efficiently enough in practice. The transformations quickly stabilize or form cycles among relatively small numbers.
In conclusion, the Happy Number problem is a clever exercise in identifying cycles within a number transformation process. With multiple approaches available, you can choose the one that best fits your coding style—be it using a hash set for intuitive tracking or employing the fast-slow pointer method for constant space optimization.
Discussion