Compute currency conversion via graph search
Company: Uber
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates graph modeling and pathfinding with weighted edges, numerical reasoning about multiplicative rates and floating-point precision, and the ability to analyze algorithmic time and space complexity.
Currency Conversion via Graph Search
Constraints
- 0 <= number of pairs == len(ratios) <= 10^4
- Each ratio is a positive real number (ratio > 0).
- Currency codes are short non-empty strings.
- Edges are directed: a pair (a, b) does NOT imply an edge b -> a.
- If src == dst, return 1.0 even if the currency never appears in any pair.
Examples
Input: ([['USD','CAD'],['CAD','EUR']], [2.0, 1.5], 'USD', 'EUR')
Expected Output: 3.0
Explanation: USD->CAD (2.0) then CAD->EUR (1.5): 2.0 * 1.5 = 3.0.
Input: ([['USD','CAD'],['CAD','EUR']], [2.0, 1.5], 'USD', 'CAD')
Expected Output: 2.0
Explanation: Single direct edge USD->CAD with ratio 2.0.
Input: ([['USD','CAD'],['CAD','EUR']], [2.0, 1.5], 'EUR', 'USD')
Expected Output: -1.0
Explanation: Edges are directed; there is no path from EUR back to USD, so return -1.0.
Input: ([['USD','CAD']], [2.0], 'USD', 'JPY')
Expected Output: -1.0
Explanation: JPY does not appear as a reachable node, so no path exists.
Input: ([], [], 'USD', 'CAD')
Expected Output: -1.0
Explanation: Empty graph: no edges, so src and dst are unconnected.
Input: ([['A','B'],['B','C'],['C','D']], [2.0, 3.0, 0.5], 'A', 'D')
Expected Output: 3.0
Explanation: A->B->C->D: 2.0 * 3.0 * 0.5 = 3.0.
Input: ([['A','B']], [5.0], 'A', 'A')
Expected Output: 1.0
Explanation: Source equals destination, so the conversion ratio is 1.0 by definition.
Hints
- Build an adjacency list: map each currency to a list of (neighbor, ratio) edges.
- Run BFS or DFS from src, carrying the running product of ratios; stop when you reach dst.
- Use a visited set to avoid cycles. In a consistent graph every src->dst path gives the same product, so the first one found is correct.
- Handle the src == dst case (return 1.0) and the unreachable case (return -1.0) explicitly.
Best Achievable Currency Rate (Non-Reciprocal Conversions)
Constraints
- 0 <= number of pairs == len(ratios) <= number of edges in the graph.
- Each ratio is a positive real number; a->b and b->a may have unrelated ratios.
- Paths considered are simple (no repeated node), so cycles cannot inflate the product unboundedly.
- Return the maximum product over all simple src->dst paths, or -1.0 if dst is unreachable.
- If src == dst, return 1.0.
Examples
Input: ([['USD','CAD'],['CAD','USD']], [2.0, 0.4], 'USD', 'CAD')
Expected Output: 2.0
Explanation: Edges are non-reciprocal (USD->CAD is 2.0, CAD->USD is 0.4). Only the direct USD->CAD path reaches CAD, giving 2.0.
Input: ([['A','B'],['A','C'],['C','B']], [2.0, 4.0, 3.0], 'A', 'B')
Expected Output: 12.0
Explanation: Direct A->B = 2.0; detour A->C->B = 4.0 * 3.0 = 12.0. The maximum is 12.0.
Input: ([['A','B'],['B','A'],['A','C']], [10.0, 0.05, 2.0], 'A', 'C')
Expected Output: 2.0
Explanation: A->C = 2.0. The A->B->A loop is blocked by the simple-path rule, so it cannot revisit A; best is 2.0.
Input: ([['A','B'],['B','C'],['A','C']], [2.0, 5.0, 3.0], 'A', 'C')
Expected Output: 10.0
Explanation: Direct A->C = 3.0; A->B->C = 2.0 * 5.0 = 10.0. Maximum is 10.0.
Input: ([['USD','CAD']], [2.0], 'USD', 'JPY')
Expected Output: -1.0
Explanation: JPY is unreachable, so return -1.0.
Input: ([], [], 'X', 'Y')
Expected Output: -1.0
Explanation: Empty graph: no path from X to Y.
Input: ([['A','B']], [7.0], 'A', 'A')
Expected Output: 1.0
Explanation: Source equals destination, ratio is 1.0.
Input: ([['A','B'],['B','C'],['C','D'],['A','D']], [2.0, 2.0, 2.0, 3.0], 'A', 'D')
Expected Output: 8.0
Explanation: Direct A->D = 3.0; A->B->C->D = 2.0 * 2.0 * 2.0 = 8.0. Maximum is 8.0.
Hints
- Maximizing a product of positive weights is equivalent to minimizing the sum of -log(weight); a shortest-path framing falls out naturally.
- If every ratio is <= 1 the -log weights are non-negative and Dijkstra works; otherwise some weights are negative and you need Bellman-Ford (which also detects arbitrage cycles).
- A direct approach: DFS every simple path (track a visited set, multiply ratios, keep the running maximum at dst), backtracking by removing nodes from visited.
- Restricting to simple paths is what makes the search terminate; a positive-product cycle would otherwise let the product grow without bound (arbitrage).