LeetCode 592 - Fraction Addition and Subtraction
The problem requires computing the result of a mathematical expression containing fractions with addition and subtraction operators. The input is a single string, expression, which consists of positive or negative fractions in the format numerator/denominator.
Difficulty: 🟡 Medium
Topics: Math, String, Simulation
Solution
Problem Understanding
The problem requires computing the result of a mathematical expression containing fractions with addition and subtraction operators. The input is a single string, expression, which consists of positive or negative fractions in the format numerator/denominator. The output must be an irreducible fraction, represented as a string in the same format. If the result is an integer, it should be represented as integer/1.
The key constraints are that each fraction in the input is irreducible, with numerators and denominators between 1 and 10, and the total number of fractions is small, between 1 and 10. These constraints imply that the solution does not need to worry about extremely large numbers of fractions or extremely large numerators/denominators, but correct arithmetic and simplification are crucial.
Edge cases that could trip up a naive implementation include negative fractions, sequences of consecutive additions and subtractions, and results that reduce to zero or to an integer. For example, handling -1/2+1/2 must return 0/1, not 0 or an incorrect fraction. The input guarantees valid fractions, so parsing can assume the format is correct, but simplification must be performed at the end.
Approaches
A brute-force approach would be to parse each fraction, convert it to a floating-point number, sum them all, and then attempt to convert the sum back into a fraction. While this works for small inputs, floating-point arithmetic introduces precision errors, and converting back to an irreducible fraction can be tricky. This method is unreliable because the output must be exact and irreducible.
The optimal approach uses exact arithmetic by maintaining numerators and denominators separately. For each fraction, we compute a common denominator and perform addition or subtraction using integer arithmetic. After processing all fractions, we simplify the result using the greatest common divisor (GCD) to ensure the fraction is irreducible. This method guarantees precision and correctness for any input within the constraints.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(n) | Convert to floats and back; may produce precision errors |
| Optimal | O(n) | O(1) | Use integer arithmetic with GCD to maintain exact fractions |
Algorithm Walkthrough
- Initialize a numerator
numand denominatordento represent the running sum, starting at0/1. - Parse the input string to extract fractions, taking care to handle positive and negative signs explicitly.
- For each fraction, extract its numerator and denominator.
- Compute the new numerator for the running sum using the formula
num * frac_den + frac_num * den, wherefrac_numandfrac_denare the numerator and denominator of the current fraction. - Update the denominator to be
den * frac_dento maintain a common denominator. - Reduce the resulting numerator and denominator by their greatest common divisor (GCD) after processing all fractions.
- Return the result as a string in the format
numerator/denominator.
Why it works: This algorithm maintains an invariant that the running sum is always represented as a single fraction. Each addition or subtraction is performed using exact integer arithmetic, so no precision is lost. Simplifying at the end guarantees the result is irreducible, satisfying the problem constraints.
Python Solution
from math import gcd
class Solution:
def fractionAddition(self, expression: str) -> str:
import re
# Initialize running numerator and denominator
num, den = 0, 1
# Extract all fractions with their signs
fractions = re.findall(r'[+-]?\d+/\d+', expression)
for frac in fractions:
n, d = map(int, frac.split('/'))
# Compute the new numerator with common denominator
num = num * d + n * den
den *= d
# Reduce fraction at each step to avoid overflow
g = gcd(abs(num), den)
num //= g
den //= g
return f"{num}/{den}"
The Python solution uses regex to reliably extract each fraction, including its sign. For each fraction, we update the running numerator and denominator using integer arithmetic. We apply GCD at each step to keep the numbers small and simplify the result. Finally, the fraction is formatted as a string.
Go Solution
package main
import (
"fmt"
"strconv"
"strings"
)
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
func fractionAddition(expression string) string {
num, den := 0, 1
i := 0
for i < len(expression) {
sign := 1
if expression[i] == '+' || expression[i] == '-' {
if expression[i] == '-' {
sign = -1
}
i++
}
j := i
for j < len(expression) && expression[j] != '+' && expression[j] != '-' {
j++
}
frac := expression[i:j]
parts := strings.Split(frac, "/")
n, _ := strconv.Atoi(parts[0])
d, _ := strconv.Atoi(parts[1])
n *= sign
num = num*d + n*den
den *= d
g := gcd(abs(num), den)
num /= g
den /= g
i = j
}
return fmt.Sprintf("%d/%d", num, den)
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
In Go, parsing requires manually iterating through the string because Go does not have as concise regex as Python. The logic for updating the numerator, denominator, and applying GCD remains the same. We handle signs explicitly, and the fraction is formatted using fmt.Sprintf.
Worked Examples
Example 1: "-1/2+1/2"
| Step | num | den | Fraction processed |
|---|---|---|---|
| Init | 0 | 1 | - |
| "-1/2" | -1 | 2 | -1/2 |
| "+1/2" | 0 | 1 | 0/1 |
Result: "0/1"
Example 2: "-1/2+1/2+1/3"
| Step | num | den | Fraction processed |
|---|---|---|---|
| Init | 0 | 1 | - |
| "-1/2" | -1 | 2 | -1/2 |
| "+1/2" | 0 | 1 | 0/1 |
| "+1/3" | 1 | 3 | 1/3 |
Result: "1/3"
Example 3: "1/3-1/2"
| Step | num | den | Fraction processed |
|---|---|---|---|
| Init | 0 | 1 | - |
| "1/3" | 1 | 3 | 1/3 |
| "-1/2" | -1 | 6 | -1/6 |
Result: "-1/6"
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We iterate through each fraction exactly once, performing integer arithmetic and GCD operations. |
| Space | O(n) | Space for storing parsed fractions (in Python) or constant in Go if parsed on the fly. |
The algorithm is efficient because each fraction is processed in constant time, and the number of fractions is small, bounded by 10. Simplification with GCD ensures the numbers remain manageable.
Test Cases
# Provided examples
assert Solution().fractionAddition("-1/2+1/2") == "0/1" # sum to zero
assert Solution().fractionAddition("-1/2+1/2+1/3") == "1/3" # positive result
assert Solution().fractionAddition("1/3-1/2") == "-1/6" # negative result
# Boundary values
assert Solution().fractionAddition("1/1") == "1/1" # single integer fraction
assert Solution().fractionAddition("-1/1") == "-1/1" # negative integer fraction
# Multiple fractions with simplification
assert Solution().fractionAddition("1/2+1/4+1/4") == "1/1" # sums to integer
assert Solution().fractionAddition("1/2-1/4+1/8") == "3/8" # mixed operations
| Test | Why |
|---|---|
| "-1/2+1/2" | sum to zero, handles negative and positive cancellation |
| "-1/2+1/2+1/3" | sum includes non-zero positive fraction |
| "1/3-1/2" | result is negative and requires simplification |
| "1/1" | single fraction, integer format |
| "-1/1" | single negative fraction |
| "1/2+1/4+1/4" | sums to integer, checks simplification |
| "1/2-1/4+1/8" | mixed signs and fractions requiring common denominator |
Edge Cases
One important edge case is when the result is zero. A naive implementation may return 0 instead of 0/1. By maintaining numerator and denominator separately and simplifying at the