Evaluate Division solution and explanation
9 more videos on this problem
Evaluate Division - Leetcode 399 - PythonNeetCodeIO · 17:37 · 62K views
Leetcode 399. Evaluate DivisionFraz · 20:06 · 16K views
Evaluate Division || Array - Math - Graphs || DFS || Graph Theory || Leetcode 399 || C++/Java/PythonAryan Mittal · 28:09 · 11K views
Leetcode 399 - Evaluate Division (Union Find)The Coachable Dev · 9:21 · 2K views
399. Evaluate Division | GRAPHS | | LeetCode Daily Challenge POTDDeep Tech · 19:28 · 1K views
Leetcode 399 Evaluate Division (Java)TheAnalyticGuy · 10:49 · 1K views
Evaluate Division - LeetCode 399 - Python SolutionCode with Carter · 11:38 · 1K views
LeetCode 399. Evaluate DivisionHappy Coding · 15:38 · 1K views
399. Evaluate Division 除法求值 【LeetCode 力扣官方题解】LeetCode 力扣 · 22:03 · 849 views
Evaluate Division | LeetCode 399 | September LeetCode Challenge | Day 27Learn Code Repeat · 20:20 · 781 views
The problem
You are given an array of variable pairs equations and an array of real numbers values, where equations[i] = [Ai, Bi] and values[i] represent the equation Ai / Bi = values[i]. Each Ai or Bi is a string that represents a single variable.
You are also given some queries, where queries[j] = [Cj, Dj] represents the jth query where you must find the answer for Cj / Dj = ?.
Return the answers to all queries. If a single answer cannot be determined, return -1.0.
Note: The input is always valid. You may assume that evaluating the queries will not result in division by zero and that there is no contradiction.
Note: The variables that do not occur in the list of equations are undefined, so the answer cannot be determined for them.
Example 1:
Input: equations = [["a","b"],["b","c"]], values = [2.0,3.0], queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]] Output: [6.00000,0.50000,-1.00000,1.00000,-1.00000] Explanation: Given: a / b = 2.0, b / c = 3.0 queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? return: [6.0, 0.5, -1.0, 1.0, -1.0 ] note: x is undefined => -1.0
Example 2:
Input: equations = [["a","b"],["b","c"],["bc","cd"]], values = [1.5,2.5,5.0], queries = [["a","c"],["c","b"],["bc","cd"],["cd","bc"]] Output: [3.75000,0.40000,5.00000,0.20000]
Example 3:
Input: equations = [["a","b"]], values = [0.5], queries = [["a","b"],["b","a"],["a","c"],["x","y"]] Output: [0.50000,2.00000,-1.00000,-1.00000]
Constraints:
1 <= equations.length <= 20equations[i].length == 21 <= Ai.length, Bi.length <= 5values.length == equations.length0.0 < values[i] <= 20.01 <= queries.length <= 20queries[i].length == 21 <= Cj.length, Dj.length <= 5Ai, Bi, Cj, Djconsist of lower case English letters and digits.
Approach overview
Problem Overview: You receive equations like a / b = 2.0 and must answer queries such as a / c. If the relationships exist through other variables, compute the result using those connections. If no path exists between two variables, return -1.0.
Approach 1: Graph Representation and DFS/BFS (Time: O(Q * (V + E)), Space: O(V + E))
Model the equations as a weighted graph. Each variable becomes a node, and every equation a / b = k creates two edges: a → b with weight k and b → a with weight 1/k. To answer a query like x / y, run a Depth-First Search or Breadth-First Search starting from x. Multiply edge weights along the path until you reach y. If a path exists, the product of weights gives the answer; otherwise return -1.0.
This approach works because ratios compose multiplicatively along paths in the graph. During traversal, maintain a visited set to avoid cycles. DFS is commonly used because the graph is usually small, but BFS works equally well. Building the adjacency list takes O(E) time where E is the number of equations, and each query may traverse the graph once.
Problems like this are classic graph traversal tasks and commonly appear alongside Depth-First Search interview questions.
Approach 2: Union-Find with Path Compression (Time: ~O((E + Q) α(N)), Space: O(N))
Another approach treats each variable as part of a disjoint set. Instead of storing only connectivity, store a weight representing the ratio between a node and its parent. When processing an equation a / b = k, union the sets while preserving the ratio relationship. The parent links maintain multiplicative weights so any node can compute its value relative to the set root.
When answering a query x / y, check whether both variables share the same root. If they do, compute the ratio using their stored weights relative to that root. If they belong to different sets, the relationship is undefined and the answer is -1.0. Path compression keeps the structure shallow and maintains correct weight adjustments.
This weighted Union-Find technique is common in problems involving relative relationships or ratios. It combines Union-Find with algebraic weight propagation to answer queries efficiently.
Recommended for interviews: Start with the graph + DFS explanation because it directly models the problem and demonstrates strong understanding of graph traversal. Many candidates implement this first. The Union-Find approach is more advanced and often viewed as the optimized design because it handles multiple queries efficiently with near-constant time operations.
Approach 1: Graph Representation and DFS
Approach: Represent the given variable equations as a graph. Each variable is a node, and an equation between two variables becomes a directed edge with a weight from one node to another representing the ratio. To find the result of a query, search for a path from the dividend node to the divisor node using Depth First Search (DFS). If a path is found, multiply the weights along the path to get the result. If no path is found, return -1.0 as the result cannot be determined.
from collections import defaultdict
def calcEquation(equations, values, queries):
graph = defaultdict(dict)
# Build the graph
for (dividend, divisor), value in zip(equations, values):
graph[dividend][divisor] = value
graph[divisor][dividend] = 1 / value
def dfs(start, end, visited, value):
if start == end:
return value
visited.add(start)
for neighbor, weight in graph[start].items():
if neighbor not in visited:
result = dfs(neighbor, end, visited, value * weight)
if result is not None:
return result
return None
results = []
for dividend, divisor in queries:
if dividend not in graph or divisor not in graph:
results.append(-1.0)
else:
visited = set()
result = dfs(dividend, divisor, visited, 1)
results.append(result if result is not None else -1.0)
return resultsfunction calcEquation(equations, values, queries) {
const graph = new Map();
// Build the graph
equations.forEach(([start, end], i) => {
if (!graph.has(start)) graph.set(start, new Map());
if (!graph.has(end)) graph.set(end, new Map());
graph.get(start).set(end, values[i]);
graph.get(end).set(start, 1 / values[i]);
});
function dfs(start, end, visited, product) {
if (!graph.has(start)) return -1.0;
if (start === end) return product;
visited.add(start);
const neighbors = graph.get(start);
for (const [next, value] of neighbors) {
if (!visited.has(next)) {
const result = dfs(next, end, visited, product * value);
if (result !== -1.0) return result;
}
}
visited.delete(start);
return -1.0;
}
return queries.map(([dividend, divisor]) => {
return dfs(dividend, divisor, new Set(), 1.0);
});
}The Python implementation uses a recursive DFS function to find a path from the start variable to the end variable. The graph is built using a dictionary of dictionaries, allowing easy access to directly connected nodes and the associated division value. If a direct or indirect path exists, the function calculates the result by multiplying the weights along the path. If no path can be found, it returns -1.0.
Time Complexity: O(V + E) for each query, where V is the number of variables and E is the number of equations due to the DFS traversal. Overall complexity is O(Q * (V + E)) for Q queries.
Space Complexity: O(V + E) for storing the graph.
Approach 2: Union-Find with Path Compression
Approach: Use Union-Find (Disjoint Set Union, DSU) with path compression to represent connected components of variables where each component can efficiently find the "parent" or representative of a set. Each variable is associated with a weight that represents its relative scale with respect to its parent. This method efficiently handles union operations and queries to determine the result by finding the root of each variable and combining their scales.
import java.util.*;
class Solution {
class Node {
String parent;
double ratio;
Node(String parent, double ratio) {
this.parent = parent;
this.ratio = ratio;
}
}
private Map<String, Node> parents = new HashMap<>();
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
for (int i = 0; i < equations.size(); i++) {
List<String> equation = equations.get(i);
String dividend = equation.get(0);
String divisor = equation.get(1);
double quotient = values[i];
union(dividend, divisor, quotient);
}
double[] result = new double[queries.size()];
for (int i = 0; i < queries.size(); i++) {
List<String> query = queries.get(i);
String dividend = query.get(0);
String divisor = query.get(1);
if (!parents.containsKey(dividend) || !parents.containsKey(divisor)) {
result[i] = -1.0;
} else {
Node rootDividend = find(dividend);
Node rootDivisor = find(divisor);
if (!rootDividend.parent.equals(rootDivisor.parent)) {
result[i] = -1.0;
} else {
result[i] = rootDividend.ratio / rootDivisor.ratio;
}
}
}
return result;
}
private Node find(String string) {
if (!parents.containsKey(string)) {
parents.put(string, new Node(string, 1.0));
}
Node currentNode = parents.get(string);
if (!currentNode.parent.equals(string)) {
Node parentNode = find(currentNode.parent);
parents.put(string, new Node(parentNode.parent, currentNode.ratio * parentNode.ratio));
}
return parents.get(string);
}
private void union(String dividend, String divisor, double quotient) {
Node rootDividend = find(dividend);
Node rootDivisor = find(divisor);
if (!rootDividend.parent.equals(rootDivisor.parent)) {
parents.put(rootDividend.parent, new Node(rootDivisor.parent, quotient * rootDivisor.ratio / rootDividend.ratio));
}
}
}#include <unordered_map>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<double> calcEquation(
vector<vector<string>>& equations,
vector<double>& values,
vector<vector<string>>& queries) {
unordered_map<string, pair<string, double>> parents;
// Find function with path compression
function<pair<string, double>(string)> find = [&](string v) {
if (!parents.count(v)) return make_pair(v, 1.0);
auto& [p, w] = parents[v];
if (v != p) {
auto result = find(p);
parents[v] = {result.first, w * result.second};
}
return parents[v];
};
// Union function
auto unite = [&](string v1, string v2, double ratio) {
auto [root1, weight1] = find(v1);
auto [root2, weight2] = find(v2);
if (root1 != root2) {
parents[root2] = {root1, ratio * weight1 / weight2};
}
};
for (int i = 0; i < equations.size(); ++i) {
const auto& eq = equations[i];
unite(eq[0], eq[1], values[i]);
}
vector<double> results;
for (const auto& query : queries) {
if (!parents.count(query[0]) || !parents.count(query[1])) {
results.push_back(-1.0);
continue;
}
auto [root1, weight1] = find(query[0]);
auto [root2, weight2] = find(query[1]);
if (root1 != root2) {
results.push_back(-1.0);
} else {
results.push_back(weight1 / weight2);
}
}
return results;
}
};This Java solution uses an inner class Node to represent each variable's parent and ratio relative to the parent. Union-Find operations ensure that the relative positions (and thus ratios) of variables in the connected components are maintained correctly. This efficient management allows quick resolution of division queries by comparing the ratio paths to the roots of the variables in the equations.
Time Complexity: O(E + Q * α(V)), where E is the number of equations, Q is the number of queries, V is the number of variables, and α is the inverse Ackermann function, which grows very slowly.
Space Complexity: O(V) to maintain parent and weight data for each variable.
Approach 3: Default Approach
function calcEquation(equations: string[][], values: number[], queries: string[][]): number[] {
const g: Record<string, [string, number][]> = {};
const ans = Array.from({ length: queries.length }, () => -1);
for (let i = 0; i < equations.length; i++) {
const [a, b] = equations[i];
(g[a] ??= []).push([b, values[i]]);
(g[b] ??= []).push([a, 1 / values[i]]);
}
for (let i = 0; i < queries.length; i++) {
const [c, d] = queries[i];
const vis = new Set<string>();
const q: [string, number][] = [[c, 1]];
if (!g[c] || !g[d]) continue;
if (c === d) {
ans[i] = 1;
continue;
}
for (const [current, v] of q) {
if (vis.has(current)) continue;
vis.add(current);
for (const [intermediate, multiplier] of g[current]) {
if (vis.has(intermediate)) continue;
if (intermediate === d) {
ans[i] = v * multiplier;
break;
}
q.push([intermediate, v * multiplier]);
}
if (ans[i] !== -1) break;
}
}
return ans;
}func calcEquation(equations [][]string, values []float64, queries [][]string) []float64 {
p := make(map[string]string)
w := make(map[string]float64)
for _, e := range equations {
a, b := e[0], e[1]
p[a], p[b] = a, b
w[a], w[b] = 1.0, 1.0
}
var find func(x string) string
find = func(x string) string {
if p[x] != x {
origin := p[x]
p[x] = find(p[x])
w[x] *= w[origin]
}
return p[x]
}
for i, v := range values {
a, b := equations[i][0], equations[i][1]
pa, pb := find(a), find(b)
if pa == pb {
continue
}
p[pa] = pb
w[pa] = w[b] * v / w[a]
}
var ans []float64
for _, e := range queries {
c, d := e[0], e[1]
if p[c] == "" || p[d] == "" || find(c) != find(d) {
ans = append(ans, -1.0)
} else {
ans = append(ans, w[c]/w[d])
}
}
return ans
}use std::collections::HashMap;
#[derive(Debug)]
pub struct DSUNode {
parent: String,
weight: f64,
}
pub struct DisjointSetUnion {
nodes: HashMap<String, DSUNode>,
}
impl DisjointSetUnion {
pub fn new(equations: &Vec<Vec<String>>) -> DisjointSetUnion {
let mut nodes = HashMap::new();
for equation in equations.iter() {
for iter in equation.iter() {
nodes.insert(
iter.clone(),
DSUNode {
parent: iter.clone(),
weight: 1.0,
},
);
}
}
DisjointSetUnion { nodes }
}
pub fn find(&mut self, v: &String) -> String {
let origin = self.nodes[v].parent.clone();
if origin == *v {
return origin;
}
let root = self.find(&origin);
self.nodes.get_mut(v).unwrap().parent = root.clone();
self.nodes.get_mut(v).unwrap().weight *= self.nodes[&origin].weight;
root
}
pub fn union(&mut self, a: &String, b: &String, v: f64) {
let pa = self.find(a);
let pb = self.find(b);
if pa == pb {
return;
}
let (wa, wb) = (self.nodes[a].weight, self.nodes[b].weight);
self.nodes.get_mut(&pa).unwrap().parent = pb;
self.nodes.get_mut(&pa).unwrap().weight = (wb * v) / wa;
}
pub fn exist(&mut self, k: &String) -> bool {
self.nodes.contains_key(k)
}
pub fn calc_value(&mut self, a: &String, b: &String) -> f64 {
if !self.exist(a) || !self.exist(b) || self.find(a) != self.find(b) {
-1.0
} else {
let wa = self.nodes[a].weight;
let wb = self.nodes[b].weight;
wa / wb
}
}
}
impl Solution {
pub fn calc_equation(
equations: Vec<Vec<String>>,
values: Vec<f64>,
queries: Vec<Vec<String>>,
) -> Vec<f64> {
let mut dsu = DisjointSetUnion::new(&equations);
for (i, &v) in values.iter().enumerate() {
let (a, b) = (&equations[i][0], &equations[i][1]);
dsu.union(a, b, v);
}
let mut ans = vec![];
for querie in queries {
let (c, d) = (&querie[0], &querie[1]);
ans.push(dsu.calc_value(c, d));
}
ans
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| Graph Representation and DFS | Time Complexity: O(V + E) for each query, where V is the number of variables and E is the number of equations due to the DFS traversal. Overall complexity is O(Q * (V + E)) for Q queries. |
| Union-Find with Path Compression | Time Complexity: O(E + Q * α(V)), where E is the number of equations, Q is the number of queries, V is the number of variables, and α is the inverse Ackermann function, which grows very slowly. |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Graph + DFS | O(Q * (V + E)) | O(V + E) | General solution; easy to implement and ideal when query count is small |
| Graph + BFS | O(Q * (V + E)) | O(V + E) | Preferred when iterative traversal is easier than recursion |
| Union-Find with Weights | O((E + Q) α(N)) | O(N) | Best when many queries must be answered after building relationships |
Frequently asked questions
Is Evaluate Division easy or hard?
Evaluate Division is generally rated Medium on LeetCode. The challenge comes from modeling equations as graph relationships or implementing a weighted Union-Find structure while correctly maintaining ratio values.
How to solve Evaluate Division in O(n)?
Use a weighted Union-Find structure. Building the structure from equations takes near linear time O(E alpha(N)). Each query can then be answered in almost constant time by checking if both variables share the same root and computing the ratio from stored weights.
What is the best approach for Evaluate Division?
Two common solutions are graph traversal and weighted Union-Find. The graph approach builds an adjacency list and uses DFS or BFS to multiply ratios along a path. The Union-Find approach stores relative weights between connected variables and answers queries in near constant time using path compression.
What data structure is used in Evaluate Division?
The problem is typically solved using an adjacency list graph for DFS or BFS traversal. Another common structure is weighted Union-Find (Disjoint Set Union) where each node stores a multiplicative weight relative to its parent.
What is the time complexity of Evaluate Division?
Graph traversal solutions run in O(Q * (V + E)) where V is the number of variables, E is the number of equations, and Q is the number of queries. Weighted Union-Find improves this to roughly O((E + Q) alpha(N)), where alpha is the inverse Ackermann function and effectively constant in practice.
Evaluate Division Python or Java solution approach?
In Python, candidates often implement the graph + DFS approach using a dictionary adjacency list and recursive traversal. In Java or C++, many implementations prefer weighted Union-Find because it handles repeated queries efficiently with path compression.
Is Evaluate Division asked at Google, Amazon, or Meta?
Evaluate Division is a well-known graph and Union-Find interview problem and has appeared in coding interviews at companies like Google, Amazon, and Meta. It tests graph modeling, ratio propagation, and familiarity with disjoint set data structures.
