TL;DR
Iterate the digit-square-sum step and detect a cycle β with a hash set (O(log n) time/space) or Floydβs cycle detection (O(1) space).
Approach 1 β Cycle detection with a hash set
The insight: the sequence is fully determined by the current value, so if a value ever repeats, everything after it repeats forever. Remember every value seen; stop when you reach 1 (happy) or revisit a value (not happy).
class Solution:
def isHappy(self, n: int) -> bool:
def next_num(x: int) -> int:
total = 0
while x:
x, d = divmod(x, 10)
total += d * d
return total
seen = set()
while n != 1 and n not in seen:
seen.add(n)
n = next_num(n)
return n == 1
Walkthrough with n = 19: 19 β 82 β 68 β 100 β 1. No repeat before reaching 1, so the loop exits with n == 1 β True. For n = 2: the sequence eventually produces 4 a second time; at that point n in seen, the loop exits with n != 1 β False.
Complexity: O(log n) digits per step, and the values quickly drop below 243 (the max digit-square-sum for any number under 1000), so the sequence length before repeating is bounded by a constant. Effectively O(log n) time, O(log n) space for the set.
Approach 2 β Floydβs tortoise and hare (O(1) space)
The insight: βreaches 1 or cyclesβ is structurally identical to βdoes a linked list have a cycle,β where next_num is the next pointer. Floydβs algorithm walks a slow pointer one step and a fast pointer two steps per iteration; if there is a cycle they eventually land on the same value, and if the sequence terminates the fast pointer reaches the fixed point 1 (since next_num(1) == 1).
class Solution:
def isHappy(self, n: int) -> bool:
def next_num(x: int) -> int:
total = 0
while x:
x, d = divmod(x, 10)
total += d * d
return total
slow = n
fast = next_num(n)
while fast != 1 and slow != fast:
slow = next_num(slow)
fast = next_num(next_num(fast))
return fast == 1
Walkthrough with n = 2: slow follows 2, 4, 16, 37, ... and fast runs twice as fast through 4, 16, 58, 145, .... Because the 4 β ... β 4 loop is closed, the two pointers eventually coincide on some value that is not 1, so fast != 1 and we return False. With n = 19, fast reaches 1 first, the loop condition fast != 1 breaks, and we return True.
Complexity: O(log n) time (same bounded iteration count, constant work per step aside from digit processing), O(1) space β no set required.
Common pitfalls
- Recomputing the digit-square-sum incorrectly, e.g. summing digits instead of squares of digits.
- Forgetting that
1 is the target: with the set version you must test n == 1 as the success exit, not just βno repeat.β
- In Floydβs version, initializing
slow and fast to the same value and checking equality before advancing β the loop would exit immediately. Start fast one step ahead.
Pattern takeaway
A deterministic sequence x β f(x) on a finite (or eventually bounded) domain must either hit a fixed target or enter a cycle. Detect the cycle either by memorizing visited states (hash set) or, for O(1) space, by Floydβs tortoise-and-hare β the same trick used for cycle detection in linked lists.