LeetCode 166: Fraction to Recurring Decimal

link

The answer may look like: [-]\ [\texttt{whole}] . [\text{not-repeated digits}] [\text{repeated digits}]. For example: \frac{-23}{12} = -1.91(6).

Details

  • Since divmod(-1, 6) = (-1, 5), if we use remainder here, we would be finding decimal for \frac{5}{6}. Instead we want the decimal for \frac{1}{6} with negative sign. So, in the beginning we remember the sign and do all arithmetic operations on absolute values. In the end, we append sign.
  • After first divmod, we get: whole, numerator2 = divmod(numerator, denominator). Here, numerator2 and denominator forms a proper fraction. So, we find repeated decimal using (numerator2, denominator).
  • Although repeat happens in the decimal digits or quotients, it is easier to detect repeat using remainder. As soon as we see a remainder we have seen before, we know the quotient digits are going to repeat. So, our answer is the digits until that point. To find the boundary of not-repeated and repeated digits, we remember the position in the sequence of digits (or digit count up to then) where we see a remainder. If that position or index is i, the not-repeated part is digits[0 : i] and repeated part is digits[i : ].

For q, r = divmod(numerator, denominator), 0 \le r < \texttt{denominator}. Consequently, the longest length before a fractional digit repeats is \mathcal{O}(\texttt{denominator}). The get_repeated_decimal()‘s outer loop can have these many iterations. On each of these iterations, the inner while loop can run for \mathcal{O}(\lg{ (\texttt{denominator}) }) iterations.

So, time: \mathcal{O}(\texttt{denominator} \cdot \lg{(\texttt{denominator})}), space: \mathcal{O}(\texttt{denominator} \cdot \lg{(\texttt{denominator})}).

class Solution:
    def get_repeated_decimal(self, n, d):
        seen = {}
        digits = []
        while n > 0 and (n not in seen):
            seen[n] = len(digits)
            n *= 10
            while n < d:
                n *= 10
                digits.append(0)

            q, n = divmod(n, d)
            digits.append(q)

        if n == 0:
            return "".join(str(d) for d in digits)

        i = seen[n]
        not_repeated = "".join(str(d) for d in digits[:i])
        repeated = "".join(str(d) for d in digits[i:])
        return f"{not_repeated}({repeated})"

    def fractionToDecimal(self, numerator: int, denominator: int) -> str:
        if numerator == 0:
            return "0"

        is_negative = False if (numerator > 0) == (denominator > 0) else True
        numerator, denominator = abs(numerator), abs(denominator)

        whole, numerator = divmod(numerator, denominator)

        fraction = self.get_repeated_decimal(numerator, denominator)

        decimal = (
            ("-" if is_negative else "")
            + str(whole)
            + (f".{fraction}" if fraction else "")
        )
        return decimal

Leave a comment