LeetCode #166 Medium

Fraction to Recurring Decimal

Given two integers numerator and denominator, return the fraction as a string, with the repeating part enclosed in parentheses if it recurs.

mathhash-tablestrings
Open on LeetCode ↗
02

Intuition

Long division by hand already tells you everything. You divide, get a quotient digit, and carry the remainder forward — multiplied by 10 — as the new dividend. A decimal repeats exactly when you see a remainder you have seen before, because from that point the same sequence of divisions will replay. So the algorithm is: do long division step by step, record each remainder and the position in the result string where it appeared, and the moment a remainder repeats, insert parentheses around the substring from its first occurrence to the current position.

How to spot this pattern

Whenever a problem asks you to convert a fraction to its decimal representation — or detect a repeating cycle in a division — the shape is long-division simulation with remainder tracking. The remainder uniquely determines the future of the division, so a repeated remainder means a repeated sequence of digits.

03

Approach

1

Handle the sign and integer part separately

If exactly one of numerator or denominator is negative, the result is negative — prepend a minus sign. Work with absolute values from here. Compute the integer part with abs(numerator) // abs(denominator) and the initial remainder with abs(numerator) % abs(denominator). If the remainder is zero, there is no decimal part — return the integer as a string.

2

Simulate long division, tracking remainders

Append a dot and enter a loop. At each step, multiply the remainder by 10, divide by the denominator to get the next digit, and compute the new remainder. Before recording the digit, check whether this remainder has been seen before. Use a hash map from remainder to the index in the result string where it first appeared.

3

Insert parentheses when a remainder repeats

If the remainder appears in the map, the digits from that stored index to the current position are the repeating block. Insert ( at the stored index and ) at the end. If the remainder reaches zero, the decimal terminates — no parentheses. Time is O(d) where d is the number of distinct remainders, which is at most denominator. Space is O(d) for the map.

04

Solution

1class Solution:
2 def fractionToDecimal(self, numerator, denominator):
3 if numerator == 0:
4 return '0'
5 result = []
6 if (numerator < 0) != (denominator < 0):
7 result.append('-')
8 numer = abs(numerator)
9 denom = abs(denominator)
10 integer_part = numer // denom
11 remainder = numer % denom
12 result.append(str(integer_part))
13 if remainder == 0:
14 return ''.join(result)
15 result.append('.')
16 seen = {}
17 while remainder != 0:
18 if remainder in seen:
19 result.insert(seen[remainder], '(')
20 result.append(')')
21 break
22 seen[remainder] = len(result)
23 remainder *= 10
24 digit = remainder // denom
25 result.append(str(digit))
26 remainder = remainder % denom
27 return ''.join(result)
05

Common pitfalls

Checking remainder before recording the digit instead of after

✗ Wrong
remainder = remainder * 10
if remainder in seen:
    break
digit = remainder // denominator
remainder = remainder % denominator
✓ Right
remainder = remainder * 10
digit = remainder // denominator
remainder = remainder % denominator
if remainder in seen:
    break

The remainder that determines repetition is the one after extracting the digit, not before. Checking before means you detect a 'repeat' of the inflated remainder and miss the correct insertion point.

Forgetting to handle the sign with XOR logic

✗ Wrong
if numerator < 0 or denominator < 0:
    result.append('-')
✓ Right
if (numerator < 0) != (denominator < 0) and numerator != 0:
    result.append('-')

Using or instead of XOR makes -3 / -7 negative when it should be positive. Also, 0 should never get a minus sign, so the numerator != 0 guard is needed.

Using Python's // and % on negative numbers without taking abs first

✗ Wrong
integer_part = numerator // denominator
✓ Right
integer_part = abs(numerator) // abs(denominator)

Python's floor division rounds toward negative infinity, so -1 // 3 gives -1 instead of 0. Working with absolute values and handling the sign separately avoids this.

06

Edge cases

Numerator is 0

The result is "0" regardless of the denominator. No sign, no decimal.

Negative numerator or denominator (but not both)

The sign check adds a leading -. Using absolute values for the division avoids sign issues in the modulus operator.

Integer overflow edge: numerator = -2^31, denominator = -1

In Python, integers have arbitrary precision so this is not an issue. In C++/Java, this specific case overflows INT_MAX and must be guarded — but the Python solution handles it naturally.

07

Complexity

Time
O(d)
Space
O(d)
d is the length of the non-repeating + repeating decimal, bounded by the denominator.