592. Fraction Addition and Subtraction
Problem Description
You are given a string expression
that represents a mathematical expression containing fractions being added and subtracted. Your task is to evaluate this expression and return the result as a string.
The expression consists of fractions in the format a/b
connected by +
or -
operators. For example: "-1/2+1/2"
or "1/3-1/2"
.
The result must be returned as an irreducible fraction (simplified to its lowest terms). If the final result is a whole number, it should still be represented as a fraction with denominator 1. For instance, if the result is 2, it should be returned as "2/1"
.
Key requirements:
- Parse and evaluate the fraction arithmetic expression
- Return the result in its simplest form (reduced fraction)
- Whole numbers should be formatted as fractions with denominator 1
- The input expression will always be valid with proper fraction format
Examples:
- Input:
"-1/2+1/2"
→ Output:"0/1"
(since -1/2 + 1/2 = 0) - Input:
"1/3-1/2"
→ Output:"-1/6"
(since 1/3 - 1/2 = 2/6 - 3/6 = -1/6) - Input:
"-1/2+1/2+1/3"
→ Output:"1/3"
(since the sum equals 1/3)
Intuition
When adding or subtracting fractions, we need a common denominator. The key insight is that instead of finding the least common denominator for each pair of fractions as we go, we can use a universal common denominator that works for all possible fractions in the expression.
Since the problem constraints typically limit the denominators to small values (1 through 10), we can use the product 6 * 7 * 8 * 9 * 10 = 30240
as our universal denominator. This number is divisible by all possible denominators from 1 to 10, making it a suitable common denominator for any fraction we encounter.
The approach works as follows:
- Start with a numerator of
0
and our universal denominator30240
- For each fraction
a/b
in the expression, convert it to have the common denominator:(a * 30240/b) / 30240
- Add or subtract the converted numerators based on the operator
- After processing all fractions, we have a single fraction with numerator
x
and denominator30240
- Simplify the final fraction by dividing both numerator and denominator by their GCD
The parsing strategy handles the signs carefully - if a fraction is preceded by a -
sign, we treat it as negative. To simplify parsing, if the expression starts with a number (no sign), we prepend a +
sign to maintain consistency.
This method avoids the complexity of repeatedly finding LCMs and adjusting denominators for each operation, making the implementation cleaner and more efficient.
Learn more about Math patterns.
Solution Approach
The implementation follows these steps:
-
Initialize the result fraction: Start with numerator
x = 0
and denominatory = 30240
(which is6 * 7 * 8 * 9 * 10
). This denominator is divisible by all numbers from 1 to 10. -
Normalize the expression: If the expression starts with a digit (positive fraction), prepend a
+
sign to make parsing uniform:if expression[0].isdigit(): expression = '+' + expression
-
Parse and process each fraction: Use two pointers to iterate through the expression:
i
tracks the current positionj
finds the end of the current fraction
For each fraction:
- Determine the sign (
+
gives 1,-
gives -1) - Move
i
past the sign - Find
j
by scanning until the next+
or-
operator or end of string - Extract the fraction substring between
i
andj
-
Extract and convert each fraction:
s = expression[i:j] a, b = s.split('/')
- Split the fraction string by
/
to get numeratora
and denominatorb
- Convert the fraction to have common denominator
y
: the equivalent numerator issign * int(a) * y // int(b)
- Add this to the running total:
x += sign * int(a) * y // int(b)
- Split the fraction string by
-
Simplify the final result:
z = gcd(x, y) x //= z y //= z
- Find the GCD of the final numerator and denominator
- Divide both by the GCD to get the irreducible fraction
-
Format and return: Return the result as a string in the format
"numerator/denominator"
:return f'{x}/{y}'
The algorithm efficiently handles all fractions by using a universal common denominator, avoiding the need to compute LCMs for each pair of fractions. The time complexity is O(n) where n is the length of the expression string, and the space complexity is O(1) excluding the input string.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the expression "1/3-1/2"
step by step to illustrate the solution approach.
Step 1: Initialize
- Start with numerator
x = 0
and universal denominatory = 30240
- This represents the fraction
0/30240
Step 2: Normalize expression
- The expression starts with
'1'
(a digit), so prepend'+'
- Expression becomes:
"+1/3-1/2"
Step 3: Process first fraction "1/3"
i = 0
, pointing at'+'
- Sign is
+
(gives ussign = 1
) - Move
i
to 1 (past the sign) - Find
j
by scanning forward: stop at position 4 (the-
sign) - Extract fraction:
s = "1/3"
- Split:
a = "1"
,b = "3"
Step 4: Convert and add first fraction
- Convert
1/3
to common denominator:- New numerator =
sign * 1 * 30240 / 3 = 1 * 1 * 10080 = 10080
- New numerator =
- Update running total:
x = 0 + 10080 = 10080
- Current result:
10080/30240
Step 5: Process second fraction "1/2"
- Move
i
to 4 (the-
sign) - Sign is
-
(gives ussign = -1
) - Move
i
to 5 (past the sign) - Find
j
: scan to end of string (position 8) - Extract fraction:
s = "1/2"
- Split:
a = "1"
,b = "2"
Step 6: Convert and add second fraction
- Convert
1/2
to common denominator:- New numerator =
sign * 1 * 30240 / 2 = -1 * 1 * 15120 = -15120
- New numerator =
- Update running total:
x = 10080 + (-15120) = -5040
- Current result:
-5040/30240
Step 7: Simplify the final fraction
- Find GCD of
-5040
and30240
:- GCD(-5040, 30240) = 5040
- Divide both by GCD:
x = -5040 / 5040 = -1
y = 30240 / 5040 = 6
- Final simplified fraction:
-1/6
Step 8: Return result
- Return
"-1/6"
This matches our expected output since 1/3 - 1/2 = 2/6 - 3/6 = -1/6
.
Solution Implementation
1from math import gcd
2
3class Solution:
4 def fractionAddition(self, expression: str) -> str:
5 # Initialize numerator and common denominator
6 # Using LCM of 1-10 as common denominator (2520)
7 numerator = 0
8 common_denominator = 6 * 7 * 8 * 9 * 10 # 2520
9
10 # Handle case where expression starts with a positive fraction
11 # Add '+' sign to maintain consistent parsing pattern
12 if expression[0].isdigit():
13 expression = '+' + expression
14
15 # Parse and process each fraction in the expression
16 index = 0
17 expression_length = len(expression)
18
19 while index < expression_length:
20 # Determine sign of current fraction
21 sign = -1 if expression[index] == '-' else 1
22 index += 1
23
24 # Find the end of current fraction (next sign or end of string)
25 fraction_end = index
26 while fraction_end < expression_length and expression[fraction_end] not in '+-':
27 fraction_end += 1
28
29 # Extract and parse the fraction
30 fraction_string = expression[index:fraction_end]
31 fraction_numerator, fraction_denominator = fraction_string.split('/')
32
33 # Add current fraction to accumulated result
34 # Convert to common denominator before adding
35 numerator += sign * int(fraction_numerator) * common_denominator // int(fraction_denominator)
36
37 # Move to next fraction
38 index = fraction_end
39
40 # Simplify the final fraction by dividing by GCD
41 greatest_common_divisor = gcd(numerator, common_denominator)
42 numerator //= greatest_common_divisor
43 common_denominator //= greatest_common_divisor
44
45 # Return the simplified fraction as string
46 return f'{numerator}/{common_denominator}'
47
1class Solution {
2 /**
3 * Adds/subtracts fractions in the given expression and returns the result in lowest terms.
4 * @param expression String containing fractions with '+' or '-' operators (e.g., "-1/2+1/3")
5 * @return The result as a fraction string in lowest terms (e.g., "-1/6")
6 */
7 public String fractionAddition(String expression) {
8 // Initialize numerator and denominator for the result
9 // Use LCM of 2,3,4,5,6,7,8,9,10 as common denominator (2520)
10 int numerator = 0;
11 int commonDenominator = 6 * 7 * 8 * 9 * 10; // 2520 = LCM(1,2,...,10)
12
13 // Add leading '+' if expression starts with a number (not a sign)
14 if (Character.isDigit(expression.charAt(0))) {
15 expression = "+" + expression;
16 }
17
18 // Parse the expression
19 int index = 0;
20 int length = expression.length();
21
22 while (index < length) {
23 // Determine the sign of the current fraction
24 int sign = expression.charAt(index) == '-' ? -1 : 1;
25 index++;
26
27 // Find the end of the current fraction
28 int endIndex = index;
29 while (endIndex < length &&
30 expression.charAt(endIndex) != '+' &&
31 expression.charAt(endIndex) != '-') {
32 endIndex++;
33 }
34
35 // Extract the fraction string (e.g., "1/2")
36 String fractionString = expression.substring(index, endIndex);
37
38 // Split the fraction into numerator and denominator
39 String[] fractionParts = fractionString.split("/");
40 int currentNumerator = Integer.parseInt(fractionParts[0]);
41 int currentDenominator = Integer.parseInt(fractionParts[1]);
42
43 // Add the current fraction to the result using common denominator
44 numerator += sign * currentNumerator * commonDenominator / currentDenominator;
45
46 // Move to the next fraction
47 index = endIndex;
48 }
49
50 // Reduce the fraction to lowest terms
51 int gcdValue = gcd(Math.abs(numerator), commonDenominator);
52 numerator /= gcdValue;
53 commonDenominator /= gcdValue;
54
55 // Return the result as a string
56 return numerator + "/" + commonDenominator;
57 }
58
59 /**
60 * Calculates the greatest common divisor using Euclidean algorithm.
61 * @param a First number
62 * @param b Second number
63 * @return The GCD of a and b
64 */
65 private int gcd(int a, int b) {
66 return b == 0 ? a : gcd(b, a % b);
67 }
68}
69
1/**
2 * Adds and subtracts fractions from a string expression and returns the result as an irreducible fraction
3 * @param expression - String containing fractions to add/subtract (e.g., "-1/2+1/3")
4 * @return The result as an irreducible fraction string (e.g., "-1/6")
5 */
6class Solution {
7public:
8 string fractionAddition(string expression) {
9 // Initialize the result fraction as 0/1
10 int numerator = 0;
11 int denominator = 1;
12
13 // Ensure expression starts with a sign for consistent parsing
14 if (expression[0] != '-' && expression[0] != '+') {
15 expression = '+' + expression;
16 }
17
18 int currentIndex = 0;
19 int expressionLength = expression.length();
20
21 // Process each fraction in the expression
22 while (currentIndex < expressionLength) {
23 // Determine if current fraction should be added or subtracted
24 int sign = (expression[currentIndex] == '-') ? -1 : 1;
25 currentIndex++;
26
27 // Find the end of current fraction (next sign or end of string)
28 int endIndex = currentIndex;
29 while (endIndex < expressionLength &&
30 expression[endIndex] != '+' &&
31 expression[endIndex] != '-') {
32 endIndex++;
33 }
34
35 // Extract the current fraction substring
36 string fractionString = expression.substr(currentIndex, endIndex - currentIndex);
37
38 // Parse the numerator and denominator from the fraction string
39 int slashPos = fractionString.find('/');
40 int currentNumerator = stoi(fractionString.substr(0, slashPos));
41 int currentDenominator = stoi(fractionString.substr(slashPos + 1));
42
43 // Add/subtract current fraction to/from the accumulated result
44 // Using formula: a/b + c/d = (a*d + c*b) / (b*d)
45 numerator = numerator * currentDenominator + sign * currentNumerator * denominator;
46 denominator *= currentDenominator;
47
48 // Move to next fraction
49 currentIndex = endIndex;
50 }
51
52 // Reduce the fraction to its simplest form
53 int gcdValue = calculateGCD(abs(numerator), abs(denominator));
54 numerator /= gcdValue;
55 denominator /= gcdValue;
56
57 // Return the result as a fraction string
58 return to_string(numerator) + "/" + to_string(denominator);
59 }
60
61private:
62 /**
63 * Calculate Greatest Common Divisor using Euclidean algorithm
64 * @param a - First number
65 * @param b - Second number
66 * @return The GCD of a and b
67 */
68 int calculateGCD(int a, int b) {
69 while (b != 0) {
70 int temp = b;
71 b = a % b;
72 a = temp;
73 }
74 return a;
75 }
76};
77
1/**
2 * Adds and subtracts fractions from a string expression and returns the result as an irreducible fraction
3 * @param expression - String containing fractions to add/subtract (e.g., "-1/2+1/3")
4 * @returns The result as an irreducible fraction string (e.g., "-1/6")
5 */
6function fractionAddition(expression: string): string {
7 // Initialize the result fraction as 0/1
8 let numerator: number = 0;
9 let denominator: number = 1;
10
11 // Ensure expression starts with a sign for consistent parsing
12 if (!expression.startsWith('-') && !expression.startsWith('+')) {
13 expression = '+' + expression;
14 }
15
16 let currentIndex: number = 0;
17 const expressionLength: number = expression.length;
18
19 // Process each fraction in the expression
20 while (currentIndex < expressionLength) {
21 // Determine if current fraction should be added or subtracted
22 const sign: number = expression[currentIndex] === '-' ? -1 : 1;
23 currentIndex++;
24
25 // Find the end of current fraction (next sign or end of string)
26 let endIndex: number = currentIndex;
27 while (endIndex < expressionLength &&
28 expression[endIndex] !== '+' &&
29 expression[endIndex] !== '-') {
30 endIndex++;
31 }
32
33 // Extract and parse the current fraction
34 const fractionString: string = expression.slice(currentIndex, endIndex);
35 const [currentNumerator, currentDenominator]: number[] = fractionString
36 .split('/')
37 .map(Number);
38
39 // Add/subtract current fraction to/from the accumulated result
40 // Using formula: a/b + c/d = (a*d + c*b) / (b*d)
41 numerator = numerator * currentDenominator + sign * currentNumerator * denominator;
42 denominator *= currentDenominator;
43
44 // Move to next fraction
45 currentIndex = endIndex;
46 }
47
48 // Helper function to calculate Greatest Common Divisor using Euclidean algorithm
49 const calculateGCD = (a: number, b: number): number => {
50 while (b !== 0) {
51 [a, b] = [b, a % b];
52 }
53 return Math.abs(a);
54 };
55
56 // Reduce the fraction to its simplest form
57 const gcdValue: number = calculateGCD(numerator, denominator);
58 numerator = Math.floor(numerator / gcdValue);
59 denominator = Math.floor(denominator / gcdValue);
60
61 // Return the result as a fraction string
62 return `${numerator}/${denominator}`;
63}
64
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the expression string.
The algorithm performs a single pass through the expression string using a while loop that iterates from index 0 to n. Within each iteration:
- Finding the next operator sign (
+
or-
) takesO(1)
time - The inner while loop to find the end of the current fraction moves the pointer
j
forward, but each character is visited only once across all iterations - Splitting the fraction string by
/
takesO(k)
wherek
is the length of the current fraction substring (bounded by a constant since denominators and numerators are limited to at most 2 digits) - Integer operations (multiplication, division) take
O(1)
time - The
gcd
operation at the end takesO(log(min(x, y)))
time, which isO(log(y))
=O(1)
sincey
is bounded by a constant (6 * 7 * 8 * 9 * 10)
Since each character in the expression is processed exactly once and all operations within the loop are constant time, the overall time complexity is O(n)
.
Space Complexity: O(1)
The algorithm uses only a fixed amount of extra space:
- Integer variables
x
,y
,i
,j
,n
,sign
, andz
- String variables
s
,a
, andb
which store fractions (bounded by constant size) - The output string construction also uses constant space
The space used does not grow with the input size, resulting in O(1)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow with Large Common Denominator
The code uses common_denominator = 6 * 7 * 8 * 9 * 10 = 30240
, which is unnecessarily large. When processing multiple fractions, the intermediate calculations (sign * int(fraction_numerator) * common_denominator // int(fraction_denominator)
) can cause integer overflow issues in languages with fixed integer sizes, or lead to inefficient computations.
Solution: Use a smaller, more efficient common denominator or calculate the LCM dynamically:
def fractionAddition(self, expression: str) -> str:
numerator = 0
denominator = 1 # Start with denominator 1
if expression[0].isdigit():
expression = '+' + expression
index = 0
while index < len(expression):
sign = -1 if expression[index] == '-' else 1
index += 1
fraction_end = index
while fraction_end < len(expression) and expression[fraction_end] not in '+-':
fraction_end += 1
fraction_string = expression[index:fraction_end]
frac_num, frac_den = map(int, fraction_string.split('/'))
# Calculate LCM of current denominator and new fraction's denominator
lcm = denominator * frac_den // gcd(denominator, frac_den)
# Scale both fractions to have the LCM as denominator
numerator = numerator * (lcm // denominator) + sign * frac_num * (lcm // frac_den)
denominator = lcm
# Reduce after each operation to prevent overflow
g = gcd(abs(numerator), denominator)
numerator //= g
denominator //= g
index = fraction_end
return f'{numerator}/{denominator}'
2. Handling Edge Cases with Leading Signs
The current approach adds a '+' sign only when the expression starts with a digit. This fails to handle cases where the expression might start with a negative fraction that has no explicit sign for the numerator (though the problem guarantees proper format, defensive programming is important).
Solution: Use regex or more robust parsing:
import re
def fractionAddition(self, expression: str) -> str:
# Use regex to find all fractions with their signs
pattern = r'[+-]?\d+/\d+'
fractions = re.findall(pattern, expression)
numerator = 0
denominator = 1
for fraction in fractions:
# Parse each fraction
if '/' in fraction:
parts = fraction.split('/')
frac_num = int(parts[0])
frac_den = int(parts[1])
# Add to running total
numerator = numerator * frac_den + frac_num * denominator
denominator = denominator * frac_den
# Simplify to prevent overflow
g = gcd(abs(numerator), denominator)
numerator //= g
denominator //= g
return f'{numerator}/{denominator}'
3. Not Simplifying During Computation
The original code only simplifies at the end, which can lead to very large intermediate values even when the final result is small.
Solution: Simplify after each addition to keep numbers manageable:
# After adding each fraction, immediately simplify
numerator += sign * frac_num * (denominator // frac_den)
g = gcd(abs(numerator), denominator)
if g > 1: # Only divide if there's a common factor
numerator //= g
denominator //= g
These improvements make the solution more robust, efficient, and less prone to numerical issues while maintaining the same O(n) time complexity.
Which data structure is used in a depth first search?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!