Taro Logo

Equal Rational Numbers

Hard
Google logo
Google
2 views
Topics:
Strings

Given two strings s and t, each of which represents a non-negative rational number, return true if and only if they represent the same number. The strings may use parentheses to denote the repeating part of the rational number. A rational number can be represented using up to three parts: <IntegerPart>, <NonRepeatingPart>, and a <RepeatingPart>. The number will be represented in one of the following three ways:

  1. <IntegerPart>
    • For example, 12, 0, and 123.
  2. <IntegerPart>.<NonRepeatingPart>
    • For example, 0.5, 1., 2.12, and 123.0001.
  3. <IntegerPart>.<NonRepeatingPart>(<RepeatingPart>)
    • For example, 0.1(6), 1.(9), 123.00(1212).

The repeating portion of a decimal expansion is conventionally denoted within a pair of round brackets. For example: 1/6 = 0.16666666... = 0.1(6) = 0.1666(6) = 0.166(66). Can you implement a function to determine if two given rational numbers represented as strings are equal? Here are a few examples:

  • s = "0.(52)", t = "0.5(25)" should return true
  • s = "0.1666(6)", t = "0.166(66)" should return true
  • s = "0.9(9)", t = "1." should return true

Solution


Understanding the Problem

The problem asks us to determine if two strings, s and t, representing rational numbers (possibly with repeating decimal parts), are equal. The strings can have an integer part, a non-repeating decimal part, and a repeating decimal part enclosed in parentheses.

Naive Approach

A straightforward but potentially problematic approach involves converting both strings into floating-point numbers (doubles) and then comparing them. However, this can lead to issues due to the limitations of floating-point precision, especially when dealing with infinitely repeating decimals.

Issues with the Naive Approach:

  • Floating-point precision: Floating-point numbers have inherent precision limits, meaning that infinitely repeating decimals cannot be represented exactly. This can lead to rounding errors and inaccurate comparisons.
  • Edge cases: Cases like "0.9(9)" and "1." may not be handled correctly due to precision issues.

Optimal Approach: Convert to Fractions

The most reliable way to compare rational numbers with repeating decimals is to convert them into fractions (numerator/denominator). Then, we can compare the fractions by reducing them to their simplest form and checking if the numerators and denominators match.

Here's the outline of the algorithm:

  1. Parse the Strings: Extract the integer, non-repeating, and repeating parts from both input strings s and t.
  2. Convert to Fractions: Convert each string representation into a fraction. This involves the following:
    • Let the number be represented as IntegerPart.NonRepeatingPart(RepeatingPart)
    • Let I be the IntegerPart, N be NonRepeatingPart, and R be RepeatingPart.
    • The value can be expressed as I + N / (10len(N)) + R / (10len(N) * (10len(R) - 1))
    • Combine these terms to obtain a single fraction num/den
  3. Simplify Fractions: Find the greatest common divisor (GCD) of the numerator and denominator of each fraction and divide both by the GCD to reduce the fractions to their simplest form.
  4. Compare Fractions: Compare the simplified numerators and denominators of the two fractions. If they are equal, the original rational numbers are equal.

Example: Converting "0.1(6)" to a fraction:

  • IntegerPart = 0, NonRepeatingPart = 1, RepeatingPart = 6
  • Value = 0 + 1/10 + 6/(10 * 9) = 1/10 + 6/90 = 9/90 + 6/90 = 15/90 = 1/6

Edge Cases:

  • "0.9(9)" should be equal to "1."
  • Integers (e.g., "12") should be handled correctly.
  • Numbers with only a non-repeating part (e.g., "0.5") should be handled.
  • Numbers with an empty repeating part should also be handled correctly.

Code Implementation (Python)

import math

def string_to_fraction(s):
    if '.' not in s:
        integer_part = int(s)
        return integer_part, 1

    integer_part, decimal_part = s.split('.')
    integer_part = int(integer_part) if integer_part else 0

    if '(' not in decimal_part:
        if not decimal_part:
            numerator = integer_part
            denominator = 1
        else:
            numerator = int(integer_part * (10**len(decimal_part)) + int(decimal_part))
            denominator = 10**len(decimal_part)

        return numerator, denominator

    non_repeating, repeating_part = decimal_part.split('(')
    repeating = repeating_part[:-1]

    num = integer_part
    den = 1

    if non_repeating:
        num = num * (10**len(non_repeating)) + int(non_repeating)
        den = 10**len(non_repeating)

    if repeating:
        repeating_num = int(repeating)
        repeating_den = 10**len(repeating) - 1

        num = num * repeating_den + repeating_num
        den = den * repeating_den

    return num, den * (10**len(non_repeating) if non_repeating else 1)


def gcd(a, b):
    if b == 0:
        return a
    return gcd(b, a % b)


def isRationalEqual(s, t):
    num1, den1 = string_to_fraction(s)
    num2, den2 = string_to_fraction(t)

    gcd1 = gcd(num1, den1)
    num1 //= gcd1
    den1 //= gcd1

    gcd2 = gcd(num2, den2)
    num2 //= gcd2
    den2 //= gcd2

    return num1 == num2 and den1 == den2

# Example Usage:
s1 = "0.(52)"
s2 = "0.5(25)"
print(f"{s1} == {s2}: {isRationalEqual(s1, s2)}")

s3 = "0.9(9)"
s4 = "1."
print(f"{s3} == {s4}: {isRationalEqual(s3, s4)}")

s5 = "0.1666(6)"
s6 = "0.166(66)"
print(f"{s5} == {s6}: {isRationalEqual(s5, s6)}")

Explanation of the Code:

  1. string_to_fraction(s): This function parses the input string s and converts it into a fraction (numerator, denominator).
  2. gcd(a, b): This function calculates the greatest common divisor (GCD) of two numbers using the Euclidean algorithm.
  3. isRationalEqual(s, t): This function converts both strings into fractions, simplifies the fractions by dividing by their GCD, and then compares the simplified numerators and denominators.

Big O Analysis

  • Time Complexity:
    • Parsing the strings: O(1) (since the lengths of the parts are limited to 4)
    • Converting to fractions: O(1)
    • Calculating GCD: O(log(min(num, den))) where num and den are the numerator and denominator respectively. Since the numerator and denominator are limited by the problem constraints, this can be considered O(1).
    • Therefore, the overall time complexity is O(1).
  • Space Complexity:
    • O(1): The algorithm uses a constant amount of extra space to store the numerators, denominators, and intermediate values.