Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

972. Equal Rational Numbers - Leetcode Solution

Problem Description

The "Equal Rational Numbers" problem requires you to determine whether two strings, S and T, each representing a rational number, are numerically equal. These strings can represent numbers in the form of decimals, and may include repeating decimals using parentheses. For example, "0.(52)" means 0.52525252... and "0.5(25)" means 0.52525252... as well.

Your task is to write a function that returns True if the two numbers represented by S and T are equal, and False otherwise.

  • Both S and T are non-empty strings.
  • They may contain a non-repeating decimal part, and optionally a repeating part in parentheses.
  • You must handle all cases, including numbers with and without repeating decimals.

Thought Process

At first glance, it might seem that simply converting the strings to floating-point numbers and comparing them should work. However, this approach fails for repeating decimals, since floating-point representations are not exact for infinite decimals. For instance, "0.(9)" is mathematically equal to "1.0", but their floating-point representations may not match exactly.

The naive brute-force approach would be to expand both decimals to a certain length and then compare, but this is inefficient and may not always be accurate due to precision issues. Instead, we need a way to convert both numbers to a canonical (standardized) form, such as a fraction (numerator/denominator), so that both can be compared exactly.

The key insight is to parse the string, extract the integer, non-repeating, and repeating parts, and then compute the exact fraction that the decimal represents. Once both numbers are in fraction form, we can simply compare their numerators and denominators after reducing them.

Solution Approach

The solution involves parsing the string representation of each rational number and converting it to its exact fractional form. Here are the steps:

  1. Parse the Input:
    • Split the string into three parts: integer part, non-repeating decimal part, and repeating decimal part.
    • The repeating part is enclosed in parentheses, if it exists.
  2. Convert to Fraction:
    • If there is no decimal, the number is an integer.
    • If there is a non-repeating part, treat it as a finite decimal.
    • If there is a repeating part, use the formula for converting a repeating decimal to a fraction:
      • Let x = integer_part.non_repeating(repeating).
      • Let n be the length of non-repeating part, r the length of repeating part.
      • Then, the value is:
        value = integer_part + non_repeating / 10^n + repeating / (10^n * (10^r - 1))
      • Combine all parts into a single numerator and denominator.
  3. Reduce the Fraction:
    • Compute the greatest common divisor (GCD) of numerator and denominator and divide both by it.
  4. Compare Both Numbers:
    • If both reduced numerators and denominators are equal, the numbers are equal.

This method ensures exact comparison, avoiding floating-point inaccuracies and handling repeating decimals correctly.

Example Walkthrough

Let's walk through the example with S = "0.(52)" and T = "0.5(25)".

  • Parse S:
    • Integer part: 0
    • Non-repeating: ""
    • Repeating: "52"
  • Convert S to Fraction:
    • Let x = 0.(52) = x
    • 100x = 52.(52) (since repeating part is 2 digits)
    • 100x - x = 52.(52) - 0.(52) = 52
    • 99x = 52 => x = 52/99
  • Parse T:
    • Integer part: 0
    • Non-repeating: "5"
    • Repeating: "25"
  • Convert T to Fraction:
    • x = 0.5(25)
    • Let n = 1 (length of non-repeating), r = 2 (length of repeating)
    • 100x = 52.525252...
    • 10x = 5.252525...
    • Subtract: 100x - 10x = 52.525252... - 5.252525... = 47.272727...
    • 90x = 47.272727...
    • But more generally, the formula gives numerator = 525 - 5 = 520, denominator = 990 (10^3 - 10^1)
    • But since the repeating part starts after one digit, we correct: numerator = 525 - 5 = 520, denominator = 990
    • Reduce: 520/990 = 52/99
  • Compare:
    • Both S and T reduce to 52/99, so they are equal.

Time and Space Complexity

  • Brute-force approach:
    • Expanding the decimals to a fixed length and comparing: O(N), where N is the number of digits expanded. Not exact, and may require very large N for accuracy.
    • Space: O(N) for storing expanded strings.
  • Optimized approach (fraction conversion):
    • Parsing the string and computing numerator/denominator is O(L), where L is the length of the input string.
    • Reducing the fraction using GCD is O(log(max(num, den))).
    • Overall time: O(L + log M), where M is the largest numerator/denominator.
    • Space: O(1), only a few integer variables are used.

Summary

The "Equal Rational Numbers" problem is best solved by converting each string representation into its exact fractional form, rather than relying on floating-point arithmetic or brute-force expansion. By parsing the integer, non-repeating, and repeating parts, and applying the formula for converting repeating decimals to fractions, we can reduce the problem to comparing two fractions. This approach is both efficient and precise, handling all edge cases and ensuring correctness.

Code Implementation

import math

def isRationalEqual(S: str, T: str) -> bool:
    def to_fraction(s):
        if '(' in s:
            nonrep, rep = s.split('(')
            rep = rep.rstrip(')')
        else:
            nonrep, rep = s, ''
        if '.' in nonrep:
            int_part, dec_part = nonrep.split('.')
        else:
            int_part, dec_part = nonrep, ''
        int_part = int(int_part) if int_part else 0
        dec_part = dec_part if dec_part else ''
        # If no repeating part
        if not rep:
            if not dec_part:
                return (int_part, 1)
            numerator = int(str(int_part) + dec_part) if int_part != 0 else int(dec_part)
            denominator = 10 ** len(dec_part)
            return (numerator, denominator)
        # There is a repeating part
        n = len(dec_part)
        r = len(rep)
        base = int(str(int_part) + dec_part) if dec_part else int_part
        # numerator = [all decimal digits incl. repeating] - [just non-repeating]
        full = dec_part + rep
        num_full = int(full) if full else 0
        num_nonrep = int(dec_part) if dec_part else 0
        numerator = int_part * (10 ** (n + r) - 10 ** n) + num_full - num_nonrep
        denominator = 10 ** n * (10 ** r - 1)
        # If integer part is 0, numerator is just num_full - num_nonrep
        if int_part == 0:
            numerator = num_full - num_nonrep
        gcd = math.gcd(numerator, denominator)
        return (numerator // gcd, denominator // gcd)

    n1, d1 = to_fraction(S)
    n2, d2 = to_fraction(T)
    return n1 * d2 == n2 * d1
      
#include <string>
#include <numeric>
using namespace std;

class Solution {
public:
    pair<long long, long long> toFraction(string s) {
        string nonrep, rep;
        auto pos = s.find('(');
        if (pos != string::npos) {
            nonrep = s.substr(0, pos);
            rep = s.substr(pos + 1, s.size() - pos - 2);
        } else {
            nonrep = s;
            rep = "";
        }
        string int_part, dec_part;
        auto dot = nonrep.find('.');
        if (dot != string::npos) {
            int_part = nonrep.substr(0, dot);
            dec_part = nonrep.substr(dot + 1);
        } else {
            int_part = nonrep;
            dec_part = "";
        }
        long long intp = int_part.empty() ? 0 : stoll(int_part);
        // No repeating
        if (rep.empty()) {
            if (dec_part.empty())
                return {intp, 1};
            long long numerator = intp * pow10(dec_part.length()) + (dec_part.empty() ? 0 : stoll(dec_part));
            long long denominator = pow10(dec_part.length());
            long long g = gcd(numerator, denominator);
            return {numerator / g, denominator / g};
        }
        int n = dec_part.length(), r = rep.length();
        string full = dec_part + rep;
        long long num_full = full.empty() ? 0 : stoll(full);
        long long num_nonrep = dec_part.empty() ? 0 : stoll(dec_part);
        long long numerator = intp * (pow10(n + r) - pow10(n)) + num_full - num_nonrep;
        long long denominator = pow10(n) * (pow10(r) - 1);
        if (intp == 0)
            numerator = num_full - num_nonrep;
        long long g = gcd(numerator, denominator);
        return {numerator / g, denominator / g};
    }
    int pow10(int n) {
        int res = 1;
        while (n--) res *= 10;
        return res;
    }
    bool isRationalEqual(string S, string T) {
        auto f1 = toFraction(S);
        auto f2 = toFraction(T);
        return f1.first * f2.second == f2.first * f1.second;
    }
};
      
class Solution {
    public boolean isRationalEqual(String S, String T) {
        int[] f1 = toFraction(S);
        int[] f2 = toFraction(T);
        return f1[0] * f2[1] == f2[0] * f1[1];
    }
    private int[] toFraction(String s) {
        String nonrep, rep;
        int l = s.indexOf('(');
        if (l != -1) {
            nonrep = s.substring(0, l);
            rep = s.substring(l + 1, s.length() - 1);
        } else {
            nonrep = s;
            rep = "";
        }
        String[] parts = nonrep.split("\\.");
        String intPart = parts[0];
        String decPart = parts.length > 1 ? parts[1] : "";
        int intp = intPart.isEmpty() ? 0 : Integer.parseInt(intPart);
        if (rep.isEmpty()) {
            if (decPart.isEmpty())
                return new int[]{intp, 1};
            int numerator = intp * pow10(decPart.length()) + (decPart.isEmpty() ? 0 : Integer.parseInt(decPart));
            int denominator = pow10(decPart.length());
            int g = gcd(numerator, denominator);
            return new int[]{numerator / g, denominator / g};
        }
        int n = decPart.length(), r = rep.length();
        String full = decPart + rep;
        int numFull = full.isEmpty() ? 0 : Integer.parseInt(full);
        int numNonRep = decPart.isEmpty() ? 0 : Integer.parseInt(decPart);
        int numerator = intp * (pow10(n + r) - pow10(n)) + numFull - numNonRep;
        int denominator = pow10(n) * (pow10(r) - 1);
        if (intp == 0)
            numerator = numFull - numNonRep;
        int g = gcd(numerator, denominator);
        return new int[]{numerator / g, denominator / g};
    }
    private int pow10(int n) {
        int res = 1;
        while (n-- > 0) res *= 10;
        return res;
    }
    private int gcd(int a, int b) {
        while (b != 0) {
            int t = a % b;
            a = b;
            b = t;
        }
        return a;
    }
}
      
function isRationalEqual(S, T) {
    function toFraction(s) {
        let nonrep, rep;
        let l = s.indexOf('(');
        if (l !== -1) {
            nonrep = s.substring(0, l);
            rep = s.substring(l + 1, s.length - 1);
        } else {
            nonrep = s;
            rep = '';
        }
        let intPart, decPart;
        let dot = nonrep.indexOf('.');
        if (dot !== -1) {
            intPart = nonrep.substring(0, dot);
            decPart = nonrep.substring(dot + 1);
        } else {
            intPart = nonrep;
            decPart = '';
        }
        let intp = intPart ? parseInt(intPart) : 0;
        if (!rep) {
            if (!decPart)
                return [intp, 1];
            let numerator = intp * Math.pow(10, decPart.length) + (decPart ? parseInt(decPart) : 0);
            let denominator = Math.pow(10, decPart.length);
            let g = gcd(numerator, denominator);
            return [numerator / g, denominator / g];
        }
        let n = decPart.length, r = rep.length;
        let full = decPart + rep;
        let numFull = full ? parseInt(full) : 0;
        let numNonRep = decPart ? parseInt(decPart) : 0;
        let numerator = intp * (Math.pow(10, n + r) - Math.pow(10, n)) + numFull - numNonRep;
        let denominator = Math.pow(10, n) * (Math.pow(10, r) - 1);
        if (intp === 0)
            numerator = numFull - numNonRep;
        let g = gcd(numerator, denominator);
        return [numerator / g, denominator / g];
    }
    function gcd(a, b) {
        while (b !== 0) {
            let t = a % b;
            a = b;
            b = t;
        }
        return a;
    }
    let [n1, d1] = toFraction(S);
    let [n2, d2] = toFraction(T);
    return n1 * d2 === n2 * d1;
}