Fraction Addition and Subtraction
MedPrompt
Given a string expression representing an expression of fraction addition and subtraction, return the calculation result in string format.
The final result should be an irreducible fraction. If your final result is an integer, change it to the format of a fraction that has a denominator 1. So in this case, 2 should be converted to 2/1.
Example 1:
Input: expression = "-1/2+1/2"
Output: "0/1"Example 2:
Input: expression = "-1/2+1/2+1/3"
Output: "1/3"Example 3:
Input: expression = "1/3-1/2"
Output: "-1/6"
Constraints:
- The input string only contains
'0'to'9','/','+'and'-'. So does the output. - Each fraction (input and output) has the format
±numerator/denominator. If the first input fraction or the output is positive, then'+'will be omitted. - The input only contains valid irreducible fractions, where the numerator and denominator of each fraction will always be in the range
[1, 10]. If the denominator is1, it means this fraction is actually an integer in a fraction format defined above. - The number of given fractions will be in the range
[1, 10]. - The numerator and denominator of the final result are guaranteed to be valid and in the range of 32-bit int.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach leverages regular expressions to simplify the task of parsing the input string. A regex pattern is defined to identify and extract each fraction, including its sign, numerator, and denominator. These extracted fractions are then iteratively added to a running total.
Algorithm
- Initialize a running total fraction,
totalNumerator = 0andtotalDenominator = 1. - Define a regular expression pattern, such as
([+-]?)(\d+)/(\d+), to identify and capture the sign (optional), numerator, and denominator of each fraction. - Use
java.util.regex.Matcherto find all occurrences of this pattern in the inputexpression. - Iterate through each match found by the matcher:
- Extract the sign, numerator
num, and denominatordenfrom the matched groups. - Apply the sign to the numerator.
- Add the parsed fraction
num/dento the running totaltotalNumerator/totalDenominator. The new total is calculated as:newNumerator = totalNumerator * den + num * totalDenominatornewDenominator = totalDenominator * den
- To keep the numbers from growing too large, immediately simplify the new total by dividing both
newNumeratorandnewDenominatorby their Greatest Common Divisor (GCD).
- Extract the sign, numerator
- After iterating through all the fractions, format the final
totalNumeratorandtotalDenominatorinto the string"<numerator>/<denominator>"and return it.
Walkthrough
The algorithm starts by initializing a result fraction, representing the cumulative sum, to 0/1. A regular expression, such as ([+-]?)(\d+)/(\d+), is used to find all fraction components in the input string. The java.util.regex.Matcher class is employed to iterate through all matches. For each fraction c/d found, the sign, numerator c, and denominator d are parsed from the matched groups. This fraction is then added to the current total A/B using the formula A/B + c/d = (A*d + B*c) / (B*d). To prevent the intermediate numerator and denominator from becoming excessively large and to simplify the final result, we simplify the running total after each addition by dividing both the numerator and denominator by their Greatest Common Divisor (GCD). The GCD is calculated using the Euclidean algorithm. It's important to use long data types for the numerator and denominator to avoid potential integer overflow during intermediate calculations. After processing all fractions, the final result A/B is formatted into the required string A + "/" + B.
import java.util.regex.Matcher;import java.util.regex.Pattern; class Solution { public String fractionAddition(String expression) { Pattern pattern = Pattern.compile("([+-]?)(\\d+)/(\\d+)"); Matcher matcher = pattern.matcher(expression); long totalNumerator = 0; long totalDenominator = 1; while (matcher.find()) { String signStr = matcher.group(1); long num = Long.parseLong(matcher.group(2)); long den = Long.parseLong(matcher.group(3)); if (signStr.equals("-")) { num = -num; } totalNumerator = totalNumerator * den + num * totalDenominator; totalDenominator = totalDenominator * den; long commonDivisor = gcd(Math.abs(totalNumerator), totalDenominator); totalNumerator /= commonDivisor; totalDenominator /= commonDivisor; } return totalNumerator + "/" + totalDenominator; } private long gcd(long a, long b) { while (b != 0) { long temp = b; b = a % b; a = temp; } return a; }}Complexity
Time
O(L + N * log(K)), where L is the length of the expression, N is the number of fractions, and K is the magnitude of intermediate numerators/denominators. The `matcher.find()` operation scans the string, contributing O(L). The loop runs N times, with each step dominated by the GCD calculation.
Space
O(L), where L is the length of the expression. This space is used to store the input string and internal state for the regex matcher.
Trade-offs
Pros
The parsing logic is concise and declarative, handled by the regex engine.
Cons
Can be less performant than a manual scan due to the overhead of regex compilation and execution.
Regex patterns can sometimes be tricky to write and debug for all edge cases.
This is the most efficient approach. It involves manually scanning the input string from left to right in a single pass. By keeping track of the current position with an index, we parse each fraction's components (sign, numerator, denominator) and immediately incorporate it into a running sum. This avoids the overhead associated with regular expressions.
Algorithm
- Initialize a running total fraction:
totalNumerator = 0,totalDenominator = 1. - Initialize a pointer for the string:
index = 0. - Loop while
indexis less than the string length:- Parse Sign: Check the character at
index. If it's'-', the sign is -1; otherwise, it's +1. Advanceindexif a sign character ('+'or'-') is present. - Parse Numerator: Read consecutive digits starting from
indexto build the numerator value. Advanceindexpast these digits. - Parse Denominator: Skip the
'/'character. Then, read the consecutive digits that follow to build the denominator value. Advanceindexpast these digits. - Add to Total: Combine the parsed fraction
num/denwith the running totaltotalNumerator/totalDenominator. - Simplify: Calculate the GCD of the new total's numerator and denominator and divide both by it to keep the fraction simplified.
- Parse Sign: Check the character at
- After the loop, format the final
totalNumerator/totalDenominatorinto a string and return.
Walkthrough
The core idea is to maintain a running total, initialized as the fraction 0/1. We then iterate through the expression string, parsing one fraction at a time and adding it to this total. The algorithm proceeds as follows:
- Initialize
totalNumerator = 0L,totalDenominator = 1L, and a string indexi = 0. - Loop while
iis less than the length of the expression. - Inside the loop, parse the next full fraction:
- Sign: Check the character at index
i. If it's'-', the sign is negative; otherwise, it's positive. Advanceiif a sign character is found. - Numerator: Read the sequence of digits that follows to form the numerator. Advance
ipast these digits. - Denominator: Skip the
'/'character. Read the next sequence of digits to form the denominator. Advanceipast these digits.
- Sign: Check the character at index
- Once a fraction
num/denis parsed, add it to the running totaltotalNumerator/totalDenominator:new_num = totalNumerator * den + num * totalDenominatornew_den = totalDenominator * den
- To keep the numbers manageable and ensure the final result is simplified, immediately reduce the new total fraction by finding the Greatest Common Divisor (GCD) of the absolute value of the new numerator and the new denominator.
- Update
totalNumeratorandtotalDenominatorby dividing them by the GCD. After the loop completes,totalNumerator/totalDenominatorwill hold the final, simplified answer. This is then converted to the required string format.
class Solution { public String fractionAddition(String expression) { long totalNumerator = 0; long totalDenominator = 1; int i = 0; int n = expression.length(); while (i < n) { // 1. Parse sign int sign = 1; if (expression.charAt(i) == '+' || expression.charAt(i) == '-') { if (expression.charAt(i) == '-') { sign = -1; } i++; } // 2. Parse numerator long num = 0; while (i < n && Character.isDigit(expression.charAt(i))) { num = num * 10 + (expression.charAt(i) - '0'); i++; } num *= sign; // Skip '/' i++; // 3. Parse denominator long den = 0; while (i < n && Character.isDigit(expression.charAt(i))) { den = den * 10 + (expression.charAt(i) - '0'); i++; } // 4. Add fraction to total totalNumerator = totalNumerator * den + num * totalDenominator; totalDenominator = totalDenominator * den; // 5. Simplify long commonDivisor = gcd(Math.abs(totalNumerator), totalDenominator); totalNumerator /= commonDivisor; totalDenominator /= commonDivisor; } return totalNumerator + "/" + totalDenominator; } private long gcd(long a, long b) { while (b != 0) { long temp = b; b = a % b; a = temp; } return a; }}Complexity
Time
O(N * log(K)), where N is the number of fractions and K is the magnitude of intermediate values. The string is traversed once, and for each fraction, a GCD calculation is performed. This is more efficient than the regex approach due to lower constant factors.
Space
O(1) extra space, as we only use a few variables to store the running total and the current index.
Trade-offs
Pros
Highly efficient with no external library overhead for parsing.
Provides fine-grained control over the parsing logic, leading to better performance.
Cons
The implementation is more verbose compared to using regex.
Requires careful manual management of the string index to avoid errors.
Solutions
Solution
class Solution {public String fractionAddition(String expression) { int x = 0, y = 6 * 7 * 8 * 9 * 10; if (Character.isDigit(expression.charAt(0))) { expression = "+" + expression; } int i = 0, n = expression.length(); while (i < n) { int sign = expression.charAt(i) == '-' ? -1 : 1; ++i; int j = i; while (j < n && expression.charAt(j) != '+' && expression.charAt(j) != '-') { ++j; } String s = expression.substring(i, j); String[] t = s.split("/"); int a = Integer.parseInt(t[0]), b = Integer.parseInt(t[1]); x += sign * a * y / b; i = j; } int z = gcd(Math.abs(x), y); x /= z; y /= z; return x + "/" + y; }private int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }}/** * @param {string} expression * @return {string} */ var fractionAddition = function (expression) { let x = 0, y = 1; if (!expression.startsWith(" - ") && !expression.startsWith(" + ")) { expression = " + " + expression; } let i = 0; const n = expression.length; while (i < n) { const sign = expression[i] === " - " ? -1 : 1; i++; let j = i; while (j < n && expression[j] !== " + " && expression[j] !== " - ") { j++; } const [a, b] = expression.slice(i, j).split(" / ").map(Number); x = x * b + sign * a * y; y *= b; i = j; } const gcd = (a, b) => { while (b !== 0) { [a, b] = [b, a % b]; } return Math.abs(a); }; const z = gcd(x, y); x = Math.floor(x / z); y = Math.floor(y / z); return ` ${x} / ${y} `; };class Solution: def fractionAddition(self, expression: str) -> str: x, y = 0, 6 * 7 * 8 * 9 * 10 if expression[0]. isdigit(): expression = '+' + expression i, n = 0, len(expression) while i < n: sign = - 1 if expression[i] == '-' else 1 i += 1 j = i while j < n and expression[j] not in '+-': j += 1 s = expression[i: j] a, b = s . split('/') x += sign * int(a) * y // int(b) i = j z = gcd(x, y) x //= z y //= z return f ' { x } / { y } 'Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.