A clear explanation of detecting whether repeated digit-square sums eventually reach 1.
Problem Restatement
We are given a positive integer n.
We repeatedly replace n with the sum of the squares of its digits.
If this process eventually reaches 1, then n is a happy number.
If the process enters a cycle that never reaches 1, then n is not a happy number.
Return True if n is happy. Otherwise, return False.
The official problem defines the process this way: start with any positive integer, replace it by the sum of the squares of its digits, and repeat until it reaches 1 or loops forever in a cycle without 1. Return whether the number is happy.
Input and Output
| Item | Meaning |
|---|---|
| Input | A positive integer n |
| Output | True if n is happy, otherwise False |
| Main operation | Replace n by the sum of the squares of its digits |
| Stop condition | Reach 1 or repeat a previous number |
Example function shape:
def isHappy(n: int) -> bool:
...Examples
Example 1:
n = 19Apply the process:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1The process reaches 1, so the answer is:
TrueExample 2:
n = 2Apply the process:
2^2 = 4
4^2 = 16
1^2 + 6^2 = 37
3^2 + 7^2 = 58
5^2 + 8^2 = 89
8^2 + 9^2 = 145
1^2 + 4^2 + 5^2 = 42
4^2 + 2^2 = 20
2^2 + 0^2 = 4Now 4 appears again.
Once a number repeats, the same sequence will repeat forever. Since 1 was not reached, the answer is:
FalseFirst Thought: Simulate the Process
The problem directly describes a process, so the first idea is to simulate it.
For each current number:
- Split it into digits.
- Square each digit.
- Add the squares.
- Replace the current number with that sum.
The missing detail is when to stop.
If the number becomes 1, we return True.
If a number appears twice, we are in a cycle, so we return False.
Key Insight
This problem is cycle detection.
The sequence either reaches 1 or eventually repeats a previous value.
A repeated value proves that the process has entered a loop, because the next value depends only on the current value.
So we keep a set of numbers we have already seen.
If we see the same number again before reaching 1, the number is not happy.
Helper Function
We need a function that computes the next value.
For example, for n = 82:
8^2 + 2^2 = 64 + 4 = 68Implementation:
def next_number(n: int) -> int:
total = 0
while n > 0:
digit = n % 10
total += digit * digit
n //= 10
return totalThe line:
digit = n % 10gets the last digit.
The line:
n //= 10removes the last digit.
Algorithm
Use a set called seen.
While n is not 1:
- If
nis already inseen, returnFalse. - Add
ntoseen. - Replace
nwith the sum of the squares of its digits.
If the loop ends, then n == 1, so return True.
Walkthrough
Use:
n = 19Initial state:
seen = set()First iteration:
n = 19
seen = {}
next = 82Second iteration:
n = 82
seen = {19}
next = 68Third iteration:
n = 68
seen = {19, 82}
next = 100Fourth iteration:
n = 100
seen = {19, 82, 68}
next = 1Now n == 1.
Return:
TrueCorrectness
At every step, the algorithm computes exactly the transformation described in the problem: replace the current number with the sum of the squares of its digits.
If the algorithm reaches 1, then by definition the original number is happy, so returning True is correct.
If the algorithm sees a number that already appeared before, the future sequence must repeat from that number onward. The transformation is deterministic, meaning the same input always produces the same next value. Therefore, after a repeated number appears, the process is trapped in a cycle.
Since the algorithm checks for 1 before continuing, a detected cycle cannot contain 1. Returning False is correct.
The problem says every non-happy number loops endlessly in a cycle without 1, so these two cases cover all possible outcomes.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(k log n) | Each transformation scans the digits, and k transformations occur before reaching 1 or a cycle |
| Space | O(k) | The set stores previously seen values |
For normal LeetCode constraints, this behaves as constant time in practice because numbers quickly shrink into a small range.
For a 32-bit integer, the largest digit-square sum after one step is bounded by:
10 digits * 9^2 = 810After that, the sequence stays small.
Implementation
class Solution:
def isHappy(self, n: int) -> bool:
seen = set()
while n != 1:
if n in seen:
return False
seen.add(n)
n = self.next_number(n)
return True
def next_number(self, n: int) -> int:
total = 0
while n > 0:
digit = n % 10
total += digit * digit
n //= 10
return totalCode Explanation
We store previous values here:
seen = set()The loop continues until n becomes 1:
while n != 1:If n already appeared, we found a cycle:
if n in seen:
return FalseOtherwise, we record the current value:
seen.add(n)Then we move to the next value in the sequence:
n = self.next_number(n)The helper function extracts digits one by one:
digit = n % 10Then it adds the square of that digit:
total += digit * digitFinally, it removes the last digit:
n //= 10When the loop exits, n == 1, so the number is happy:
return TrueAlternative: Fast and Slow Pointers
We can also solve this without a set using Floyd’s cycle detection.
Think of each number as pointing to the next number.
For example:
19 -> 82 -> 68 -> 100 -> 1Use two runners:
| Pointer | Movement |
|---|---|
slow | Moves one step |
fast | Moves two steps |
If the sequence reaches 1, return True.
If slow and fast meet before reaching 1, there is a cycle, so return False.
class Solution:
def isHappy(self, n: int) -> bool:
slow = n
fast = self.next_number(n)
while fast != 1 and slow != fast:
slow = self.next_number(slow)
fast = self.next_number(self.next_number(fast))
return fast == 1
def next_number(self, n: int) -> int:
total = 0
while n > 0:
digit = n % 10
total += digit * digit
n //= 10
return totalThe set version is usually easier to read. The fast and slow pointer version uses O(1) extra space.
Testing
def run_tests():
s = Solution()
assert s.isHappy(19) is True
assert s.isHappy(2) is False
assert s.isHappy(1) is True
assert s.isHappy(7) is True
assert s.isHappy(4) is False
assert s.isHappy(100) is True
print("all tests passed")
run_tests()| Test | Why |
|---|---|
19 | Standard happy example |
2 | Standard unhappy example |
1 | Already happy |
7 | Reaches 1 after several steps |
4 | Enters the known unhappy cycle |
100 | Contains zeros and reaches 1 |