3387. Maximize Amount After Two Days of Conversions
Problem Description
You start with 1.0
unit of a currency called initialCurrency
. You have two days to perform currency exchanges to maximize the amount of initialCurrency
you end up with.
For each day, you're given:
- Day 1: Arrays
pairs1
andrates1
- Day 2: Arrays
pairs2
andrates2
Each pair pairs[i] = [startCurrency, targetCurrency]
with corresponding rates[i]
means you can convert from startCurrency
to targetCurrency
at the given rate. Additionally, you can always convert backwards from targetCurrency
to startCurrency
at rate 1/rate
.
The rules are:
- On Day 1, you can perform any number of currency conversions (including zero) using only the Day 1 rates
- On Day 2, you can perform any number of currency conversions (including zero) using only the Day 2 rates
- Day 2 conversions must come after Day 1 conversions
Your goal is to find the maximum amount of initialCurrency
you can have after both days.
For example, if you start with currency "A":
- On Day 1, you might convert A β B β C to end up with some amount of currency C
- On Day 2, you might convert C β D β A to convert back to the initial currency A
- The final answer would be the maximum amount of A you can obtain through any sequence of valid conversions
The solution uses graph traversal (DFS) to:
- Build a graph of all possible conversions and compute the maximum amount of each currency reachable from
initialCurrency
on Day 1 - Build another graph for Day 2 to find all possible paths back to
initialCurrency
- Find the maximum by trying all intermediate currencies - the amount you can have of currency X after Day 1, divided by the rate needed to convert from
initialCurrency
to X on Day 2 (which gives you the amount ofinitialCurrency
you'd get back)
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves currencies as nodes and exchange rates as weighted edges between them. Each currency pair represents a bidirectional edge in the graph.
Is it a tree?
- No: The currency exchange network is not a tree - it can have cycles (e.g., USD β EUR β GBP β USD) and multiple paths between currencies.
Is the problem related to directed acyclic graphs?
- No: The graph has cycles since you can convert currencies back and forth (each conversion has a reverse conversion at rate 1/r).
Is the problem related to shortest paths?
- No: We're looking for the maximum amount achievable, not shortest paths. We want to explore all reachable currencies and track the maximum amount we can have of each currency.
Does the problem involve connectivity?
- Yes: We need to find all currencies reachable from the initial currency on Day 1, and then determine which of those can be converted back to the initial currency on Day 2.
Does the problem have small constraints?
- Yes: Currency exchange problems typically have manageable constraints (limited number of currencies and exchange pairs).
DFS/backtracking?
- Yes: DFS is ideal here to traverse the graph and compute the maximum amount of each currency reachable from the initial currency.
Conclusion: The flowchart suggests using DFS to explore the currency exchange graph. The solution uses DFS twice:
- First DFS on Day 1's graph to find the maximum amount of each currency reachable from
initialCurrency
- Second DFS on Day 2's graph to find conversion rates back to
initialCurrency
- The answer is the maximum value obtained by trying all intermediate currencies as transition points between the two days
Intuition
The key insight is to think of this problem as finding the best "intermediate currency" to hold between Day 1 and Day 2.
Since we must perform Day 1 operations before Day 2 operations, we can break this into two independent subproblems:
- What's the maximum amount of each currency we can obtain starting from
initialCurrency
on Day 1? - How can we convert from each currency back to
initialCurrency
on Day 2?
Imagine you start with 1.0 unit of currency A. After Day 1, you might have converted it to some other currency X with amount amount_X
. Then on Day 2, you need to convert X back to A. The final amount of A you get is amount_X * rate(XβA on Day 2)
.
But here's the trick: if we can reach X from A on Day 2 with some rate r
, then we can go from X back to A with rate 1/r
. So if d2[X]
represents the maximum amount of X we can get starting from 1.0 unit of A on Day 2, then converting from X back to A gives us amount_X / d2[X]
units of A.
This leads to our strategy:
- Use DFS from
initialCurrency
on Day 1's graph to find the maximum amount of every reachable currency - Use DFS from
initialCurrency
on Day 2's graph to find conversion rates frominitialCurrency
to all reachable currencies - Try every possible intermediate currency X: calculate
d1[X] / d2[X]
(amount after Day 1 divided by the rate to reach X from initial on Day 2) - Return the maximum value found
The DFS is perfect here because:
- We need to explore all paths to find maximum amounts (not just shortest paths)
- The graph might have cycles, but DFS with visited tracking handles this
- We're essentially computing the "reachability with maximum value" from a source node
The beauty of this approach is that it avoids the complexity of trying all possible conversion sequences by recognizing that we only need to consider which currency to "pause" at between the two days.
Learn more about Depth-First Search, Breadth-First Search and Graph patterns.
Solution Approach
The solution implements the two-phase DFS strategy we identified. Let's walk through the implementation:
Main Function (maxAmount
):
- Build two dictionaries
d1
andd2
that store the maximum amount of each currency reachable frominitialCurrency
on Day 1 and Day 2 respectively - For each currency in
d2
, calculate the maximum amount ofinitialCurrency
obtainable by using that currency as the intermediate:d1.get(a, 0) / r2
- Return the maximum value found
Building the Graph (build
function):
The build
function constructs an adjacency list representation of the currency graph and performs DFS:
-
Graph Construction:
g = defaultdict(list) for (a, b), r in zip(pairs, rates): g[a].append((b, r)) # Forward edge with rate r g[b].append((a, 1 / r)) # Reverse edge with rate 1/r
Each currency pair creates two directed edges - one for each direction of conversion.
-
DFS Traversal:
def dfs(a: str, v: float): d[a] = v # Store max amount of currency a for b, r in g[a]: # Explore neighbors if b not in d: # Avoid revisiting dfs(b, v * r) # Multiply current amount by rate
- Start from
init
currency with amount1.0
- For each unvisited neighbor, multiply the current amount by the exchange rate
- The visited check (
if b not in d
) prevents infinite loops in cyclic graphs
- Start from
Final Calculation:
The expression max(d1.get(a, 0) / r2 for a, r2 in d2.items())
computes:
- For each currency
a
reachable on Day 2 with amountr2
from initial d1.get(a, 0)
gives the amount of currencya
after Day 1 (0 if not reachable)d1.get(a, 0) / r2
converts back to initial currency: if we haveX
units of currencya
and it takesr2
units of initial to get 1 unit ofa
on Day 2, then we can convertX
units ofa
back toX/r2
units of initial
Time Complexity: O(V + E)
for each DFS where V is the number of unique currencies and E is the number of exchange pairs. Since we run DFS twice, total is O(V + E)
.
Space Complexity: O(V + E)
for storing the graph and the distance dictionaries.
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 a concrete example to understand the solution approach.
Given:
initialCurrency = "USD"
- Day 1:
pairs1 = [["USD", "EUR"], ["EUR", "GBP"]]
,rates1 = [2.0, 3.0]
- Day 2:
pairs2 = [["GBP", "USD"], ["EUR", "USD"]]
,rates2 = [5.0, 4.0]
Step 1: Build Day 1 Graph and Find Maximum Amounts
First, we build the graph for Day 1:
- USD β EUR at rate 2.0 (and EUR β USD at rate 0.5)
- EUR β GBP at rate 3.0 (and GBP β EUR at rate 0.333...)
Starting DFS from USD with 1.0:
- Visit USD:
d1["USD"] = 1.0
- Explore USD β EUR:
d1["EUR"] = 1.0 Γ 2.0 = 2.0
- From EUR, explore EUR β GBP:
d1["GBP"] = 2.0 Γ 3.0 = 6.0
- EUR β USD is not explored (USD already visited)
Result: d1 = {"USD": 1.0, "EUR": 2.0, "GBP": 6.0}
This means after Day 1:
- We can have 1.0 USD (do nothing)
- We can have 2.0 EUR (convert USD β EUR)
- We can have 6.0 GBP (convert USD β EUR β GBP)
Step 2: Build Day 2 Graph and Find Conversion Rates from Initial Currency
Build the graph for Day 2:
- GBP β USD at rate 5.0 (and USD β GBP at rate 0.2)
- EUR β USD at rate 4.0 (and USD β EUR at rate 0.25)
Starting DFS from USD with 1.0:
- Visit USD:
d2["USD"] = 1.0
- Explore USD β GBP:
d2["GBP"] = 1.0 Γ 0.2 = 0.2
- Explore USD β EUR:
d2["EUR"] = 1.0 Γ 0.25 = 0.25
Result: d2 = {"USD": 1.0, "GBP": 0.2, "EUR": 0.25}
This means on Day 2, starting with 1.0 USD:
- To get 1 GBP, we need 5 USD (so d2["GBP"] = 0.2, meaning 1 USD gives 0.2 GBP)
- To get 1 EUR, we need 4 USD (so d2["EUR"] = 0.25, meaning 1 USD gives 0.25 EUR)
Step 3: Calculate Maximum for Each Intermediate Currency
Now we try each currency as an intermediate holding between days:
- Hold USD between days:
- After Day 1: 1.0 USD
- Converting back on Day 2:
1.0 / 1.0 = 1.0
USD
- Hold EUR between days:
- After Day 1: 2.0 EUR
- Converting back on Day 2:
2.0 / 0.25 = 8.0
USD - (Since 0.25 EUR costs 1 USD on Day 2, 2.0 EUR can be sold for 8.0 USD)
- Hold GBP between days:
- After Day 1: 6.0 GBP
- Converting back on Day 2:
6.0 / 0.2 = 30.0
USD - (Since 0.2 GBP costs 1 USD on Day 2, 6.0 GBP can be sold for 30.0 USD)
Step 4: Return Maximum
The maximum is 30.0 USD, achieved by:
- Day 1: Convert USD β EUR β GBP (1.0 β 2.0 β 6.0)
- Hold 6.0 GBP overnight
- Day 2: Convert GBP β USD at rate 5.0 (6.0 Γ 5.0 = 30.0)
The key insight is that d1[currency] / d2[currency]
gives us the final amount of initial currency when using that currency as the intermediate holding.
Solution Implementation
1from typing import List, Dict
2from collections import defaultdict
3
4class Solution:
5 def maxAmount(
6 self,
7 initialCurrency: str,
8 pairs1: List[List[str]],
9 rates1: List[float],
10 pairs2: List[List[str]],
11 rates2: List[float],
12 ) -> float:
13 # Build exchange rate dictionaries for both markets starting from initial currency
14 market1_rates = self._build_exchange_rates(pairs1, rates1, initialCurrency)
15 market2_rates = self._build_exchange_rates(pairs2, rates2, initialCurrency)
16
17 # Find maximum amount by trying all possible currencies in market2
18 # Convert initial -> currency_x in market1, then currency_x -> initial in market2
19 max_amount = 0.0
20 for currency, rate_from_initial in market2_rates.items():
21 # If currency exists in market1, calculate round-trip value
22 if currency in market1_rates:
23 # market1_rates[currency] = amount of 'currency' we get from 1 unit of initial
24 # rate_from_initial = amount of 'currency' needed to get 1 unit of initial in market2
25 # So we get market1_rates[currency] / rate_from_initial units of initial back
26 round_trip_value = market1_rates[currency] / rate_from_initial
27 max_amount = max(max_amount, round_trip_value)
28
29 return max_amount
30
31 def _build_exchange_rates(
32 self,
33 pairs: List[List[str]],
34 rates: List[float],
35 initial_currency: str
36 ) -> Dict[str, float]:
37 """
38 Build a dictionary of exchange rates from initial currency to all reachable currencies.
39 Returns a dict where key is currency and value is the amount of that currency
40 obtained from 1 unit of initial currency.
41 """
42
43 def depth_first_search(current_currency: str, current_value: float) -> None:
44 """DFS to calculate exchange rates from initial currency to all reachable currencies"""
45 # Mark this currency as visited with its exchange rate from initial
46 exchange_rates[current_currency] = current_value
47
48 # Explore all neighbors (currencies we can exchange to)
49 for next_currency, exchange_rate in adjacency_graph[current_currency]:
50 if next_currency not in exchange_rates:
51 # Calculate value when converting to next currency
52 next_value = current_value * exchange_rate
53 depth_first_search(next_currency, next_value)
54
55 # Build adjacency graph for currency exchange
56 adjacency_graph = defaultdict(list)
57 for (currency_a, currency_b), rate in zip(pairs, rates):
58 # Add bidirectional edges with appropriate rates
59 adjacency_graph[currency_a].append((currency_b, rate)) # a -> b with rate
60 adjacency_graph[currency_b].append((currency_a, 1.0 / rate)) # b -> a with 1/rate
61
62 # Dictionary to store exchange rates from initial currency
63 exchange_rates = {}
64
65 # Start DFS from initial currency with value 1.0
66 depth_first_search(initial_currency, 1.0)
67
68 return exchange_rates
69
1class Solution {
2 /**
3 * Find the maximum amount that can be obtained by converting currencies through two days
4 * @param initialCurrency The starting currency
5 * @param pairs1 Currency pairs available on day 1
6 * @param rates1 Exchange rates for day 1
7 * @param pairs2 Currency pairs available on day 2
8 * @param rates2 Exchange rates for day 2
9 * @return Maximum amount after conversions
10 */
11 public double maxAmount(String initialCurrency, List<List<String>> pairs1, double[] rates1,
12 List<List<String>> pairs2, double[] rates2) {
13 // Build conversion maps for both days starting from initial currency
14 Map<String, Double> day1Conversions = buildConversionMap(pairs1, rates1, initialCurrency);
15 Map<String, Double> day2Conversions = buildConversionMap(pairs2, rates2, initialCurrency);
16
17 double maxAmount = 0;
18
19 // Try all possible intermediate currencies
20 for (Map.Entry<String, Double> entry : day2Conversions.entrySet()) {
21 String intermediateCurrency = entry.getKey();
22 double day2Rate = entry.getValue();
23
24 // If we can reach this currency on day 1
25 if (day1Conversions.containsKey(intermediateCurrency)) {
26 // Calculate total: day1 rate to intermediate / day2 rate back to initial
27 double totalAmount = day1Conversions.get(intermediateCurrency) / day2Rate;
28 maxAmount = Math.max(maxAmount, totalAmount);
29 }
30 }
31
32 return maxAmount;
33 }
34
35 /**
36 * Build a map of all reachable currencies and their conversion rates from initial currency
37 * @param pairs List of currency pairs
38 * @param rates Exchange rates for each pair
39 * @param initialCurrency Starting currency
40 * @return Map of currency to conversion rate from initial currency
41 */
42 private Map<String, Double> buildConversionMap(List<List<String>> pairs, double[] rates,
43 String initialCurrency) {
44 // Build adjacency list representation of the currency graph
45 Map<String, List<Pair<String, Double>>> graph = new HashMap<>();
46 Map<String, Double> conversions = new HashMap<>();
47
48 // Create bidirectional edges for each currency pair
49 for (int i = 0; i < pairs.size(); i++) {
50 String currencyA = pairs.get(i).get(0);
51 String currencyB = pairs.get(i).get(1);
52 double exchangeRate = rates[i];
53
54 // Add edge from A to B with rate
55 graph.computeIfAbsent(currencyA, key -> new ArrayList<>())
56 .add(new Pair<>(currencyB, exchangeRate));
57
58 // Add edge from B to A with inverse rate
59 graph.computeIfAbsent(currencyB, key -> new ArrayList<>())
60 .add(new Pair<>(currencyA, 1.0 / exchangeRate));
61 }
62
63 // Perform DFS to find all reachable currencies and their rates
64 depthFirstSearch(graph, conversions, initialCurrency, 1.0);
65
66 return conversions;
67 }
68
69 /**
70 * DFS to explore all reachable currencies and calculate conversion rates
71 * @param graph Currency graph with exchange rates
72 * @param conversions Map to store conversion rates
73 * @param currentCurrency Current currency being explored
74 * @param currentRate Accumulated rate from initial currency
75 */
76 private void depthFirstSearch(Map<String, List<Pair<String, Double>>> graph,
77 Map<String, Double> conversions,
78 String currentCurrency,
79 double currentRate) {
80 // Skip if already visited
81 if (conversions.containsKey(currentCurrency)) {
82 return;
83 }
84
85 // Mark as visited and store conversion rate
86 conversions.put(currentCurrency, currentRate);
87
88 // Explore all neighbors
89 for (Pair<String, Double> neighbor : graph.getOrDefault(currentCurrency, List.of())) {
90 String nextCurrency = neighbor.getKey();
91 double exchangeRate = neighbor.getValue();
92
93 // Only visit unvisited currencies
94 if (!conversions.containsKey(nextCurrency)) {
95 depthFirstSearch(graph, conversions, nextCurrency, currentRate * exchangeRate);
96 }
97 }
98 }
99}
100
1class Solution {
2public:
3 double maxAmount(string initialCurrency, vector<vector<string>>& pairs1, vector<double>& rates1, vector<vector<string>>& pairs2, vector<double>& rates2) {
4 // Build exchange rate maps for both days from the initial currency
5 unordered_map<string, double> day1Rates = buildExchangeRates(pairs1, rates1, initialCurrency);
6 unordered_map<string, double> day2Rates = buildExchangeRates(pairs2, rates2, initialCurrency);
7
8 // Find the maximum amount by trying all possible intermediate currencies
9 double maxValue = 0;
10 for (const auto& [currency, rateFromInit] : day2Rates) {
11 // Check if this currency exists in day1 (can convert to it on day1)
12 if (day1Rates.find(currency) != day1Rates.end()) {
13 // Calculate: initial -> currency (day1) -> initial (day2)
14 // day1Rates[currency]: amount of 'currency' we get from 1 unit of initial
15 // 1/rateFromInit: amount of initial we get from 1 unit of 'currency' on day2
16 maxValue = max(maxValue, day1Rates[currency] / rateFromInit);
17 }
18 }
19
20 return maxValue;
21 }
22
23private:
24 unordered_map<string, double> buildExchangeRates(vector<vector<string>>& pairs, vector<double>& rates, const string& startCurrency) {
25 // Build adjacency list for currency exchange graph
26 unordered_map<string, vector<pair<string, double>>> graph;
27 unordered_map<string, double> exchangeRates;
28
29 // Create bidirectional edges for each currency pair
30 for (int i = 0; i < pairs.size(); ++i) {
31 const string& fromCurrency = pairs[i][0];
32 const string& toCurrency = pairs[i][1];
33 double exchangeRate = rates[i];
34
35 // Add edge from fromCurrency to toCurrency with given rate
36 graph[fromCurrency].push_back({toCurrency, exchangeRate});
37 // Add reverse edge with reciprocal rate
38 graph[toCurrency].push_back({fromCurrency, 1.0 / exchangeRate});
39 }
40
41 // DFS to calculate exchange rates from start currency to all reachable currencies
42 auto dfs = [&](this auto&& dfs, const string& currentCurrency, double currentRate) -> void {
43 // Skip if already visited
44 if (exchangeRates.find(currentCurrency) != exchangeRates.end()) {
45 return;
46 }
47
48 // Store the exchange rate for current currency
49 exchangeRates[currentCurrency] = currentRate;
50
51 // Explore all neighboring currencies
52 for (const auto& [nextCurrency, rate] : graph[currentCurrency]) {
53 if (exchangeRates.find(nextCurrency) == exchangeRates.end()) {
54 // Recursively calculate rate for unvisited currency
55 dfs(nextCurrency, currentRate * rate);
56 }
57 }
58 };
59
60 // Start DFS from the initial currency with rate 1.0
61 dfs(startCurrency, 1.0);
62
63 return exchangeRates;
64 }
65};
66
1// Global type definition for currency exchange pair
2type CurrencyPair = {
3 currency: string;
4 rate: number;
5};
6
7/**
8 * Finds the maximum amount obtainable by exchanging currencies through two different exchange systems
9 * @param initialCurrency - The starting currency
10 * @param pairs1 - Currency pairs for the first exchange system
11 * @param rates1 - Exchange rates for the first system
12 * @param pairs2 - Currency pairs for the second exchange system
13 * @param rates2 - Exchange rates for the second system
14 * @returns The maximum amount obtainable after exchanges
15 */
16function maxAmount(
17 initialCurrency: string,
18 pairs1: string[][],
19 rates1: number[],
20 pairs2: string[][],
21 rates2: number[],
22): number {
23 // Build exchange rate maps for both systems starting from initial currency
24 const exchangeRatesSystem1 = build(pairs1, rates1, initialCurrency);
25 const exchangeRatesSystem2 = build(pairs2, rates2, initialCurrency);
26
27 let maxValue = 0;
28
29 // Find the maximum value by trying all possible intermediate currencies
30 for (const [currency, rateFromInitial] of Object.entries(exchangeRatesSystem2)) {
31 if (currency in exchangeRatesSystem1) {
32 // Calculate the value: convert initial to currency via system1, then back via system2
33 const currentValue = exchangeRatesSystem1[currency] / rateFromInitial;
34 maxValue = Math.max(maxValue, currentValue);
35 }
36 }
37
38 return maxValue;
39}
40
41/**
42 * Builds a map of exchange rates from the initial currency to all reachable currencies
43 * @param pairs - Array of currency pairs [fromCurrency, toCurrency]
44 * @param rates - Array of exchange rates corresponding to each pair
45 * @param initialCurrency - The starting currency for rate calculations
46 * @returns Map of currency to exchange rate from initial currency
47 */
48function build(
49 pairs: string[][],
50 rates: number[],
51 initialCurrency: string
52): { [key: string]: number } {
53 // Build adjacency list representation of the currency exchange graph
54 const exchangeGraph: { [key: string]: CurrencyPair[] } = {};
55
56 // Construct bidirectional graph with exchange rates
57 for (let i = 0; i < pairs.length; i++) {
58 const fromCurrency = pairs[i][0];
59 const toCurrency = pairs[i][1];
60 const exchangeRate = rates[i];
61
62 // Initialize arrays if not present
63 if (!exchangeGraph[fromCurrency]) {
64 exchangeGraph[fromCurrency] = [];
65 }
66 if (!exchangeGraph[toCurrency]) {
67 exchangeGraph[toCurrency] = [];
68 }
69
70 // Add bidirectional edges with appropriate rates
71 exchangeGraph[fromCurrency].push({ currency: toCurrency, rate: exchangeRate });
72 exchangeGraph[toCurrency].push({ currency: fromCurrency, rate: 1 / exchangeRate });
73 }
74
75 // Map to store exchange rates from initial currency
76 const exchangeRates: { [key: string]: number } = {};
77
78 // Perform DFS to calculate all reachable exchange rates
79 dfs(exchangeGraph, exchangeRates, initialCurrency, 1.0);
80
81 return exchangeRates;
82}
83
84/**
85 * Depth-first search to calculate exchange rates from initial currency to all reachable currencies
86 * @param exchangeGraph - Graph representation of currency exchanges
87 * @param exchangeRates - Map to store calculated exchange rates
88 * @param currentCurrency - Current currency being processed
89 * @param currentRate - Accumulated exchange rate from initial currency
90 */
91function dfs(
92 exchangeGraph: { [key: string]: CurrencyPair[] },
93 exchangeRates: { [key: string]: number },
94 currentCurrency: string,
95 currentRate: number
96): void {
97 // Skip if already visited
98 if (currentCurrency in exchangeRates) {
99 return;
100 }
101
102 // Store the exchange rate for current currency
103 exchangeRates[currentCurrency] = currentRate;
104
105 // Explore all adjacent currencies
106 const neighbors = exchangeGraph[currentCurrency] || [];
107 for (const neighbor of neighbors) {
108 const nextCurrency = neighbor.currency;
109 const edgeRate = neighbor.rate;
110
111 // Only visit unvisited currencies
112 if (!(nextCurrency in exchangeRates)) {
113 dfs(exchangeGraph, exchangeRates, nextCurrency, currentRate * edgeRate);
114 }
115 }
116}
117
Time and Space Complexity
Time Complexity: O(V + E)
The algorithm consists of two main phases, each building a graph and performing DFS traversal:
-
Building graph
g
in thebuild
function:O(E)
whereE
is the number of exchange pairs, as we iterate through all pairs once and add bidirectional edges. -
DFS traversal in the
build
function:O(V + E)
whereV
is the number of unique currencies andE
is the number of edges. Each vertex is visited once (tracked by dictionaryd
), and we explore all edges from visited vertices. -
The
build
function is called twice - once forpairs1/rates1
and once forpairs2/rates2
. If we denoteV1, E1
for the first day andV2, E2
for the second day, this gives usO(V1 + E1) + O(V2 + E2)
. -
The final max computation iterates through all currencies in
d2
, which isO(V2)
.
Overall time complexity: O(V1 + E1 + V2 + E2)
which simplifies to O(V + E)
where V
represents total unique currencies and E
represents total exchange pairs across both days.
Space Complexity: O(V + E)
-
Graph
g
in eachbuild
call:O(V + E)
- stores adjacency list with all vertices and bidirectional edges. -
Dictionary
d
in eachbuild
call:O(V)
- stores exchange rate for each reachable currency. -
DFS recursion stack:
O(V)
in worst case when the graph forms a chain. -
Since
build
is called twice, we have space usage ofO(V1 + E1)
andO(V2 + E2)
.
Overall space complexity: O(V + E)
where V
is the total number of unique currencies and E
is the total number of exchange pairs.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Cycles and Infinite Loops in Currency Exchange
The Problem:
The most critical pitfall in this solution is not properly handling cycles in the currency exchange graph. Without the visited check (if next_currency not in exchange_rates
), the DFS would enter infinite loops when encountering cycles like A β B β C β A.
Why It Happens: Currency exchange graphs often contain cycles since currencies can typically be exchanged bidirectionally. Additionally, longer cycles through multiple currencies are common in real exchange systems.
The Solution:
The code correctly prevents this by tracking visited currencies in the exchange_rates
dictionary. Once a currency is visited, it won't be explored again:
if next_currency not in exchange_rates: # Critical check next_value = current_value * exchange_rate depth_first_search(next_currency, next_value)
Pitfall 2: Missing Optimal Paths Due to Early Termination
The Problem: The DFS visits each currency only once and records the first path's exchange rate. However, in a graph with cycles, there might be multiple paths to the same currency with different rates. The current implementation might miss the optimal rate if a suboptimal path is found first.
Example Scenario:
- Path 1: A β B (rate: 2.0)
- Path 2: A β C β B (rate: 3.0) If DFS explores Path 1 first, it records B with rate 2.0 and never updates it, missing the better rate of 3.0.
The Solution: For maximum exchange rates, modify the DFS to use dynamic programming or Bellman-Ford-like relaxation:
def depth_first_search(current_currency: str, current_value: float) -> None:
# Only update if we found a better rate
if current_currency not in exchange_rates or current_value > exchange_rates[current_currency]:
exchange_rates[current_currency] = current_value
# Continue exploring from this currency
for next_currency, exchange_rate in adjacency_graph[current_currency]:
next_value = current_value * exchange_rate
# Only explore if potentially better
if next_currency not in exchange_rates or next_value > exchange_rates[next_currency]:
depth_first_search(next_currency, next_value)
Pitfall 3: Handling Edge Cases
The Problem: The solution doesn't explicitly handle several edge cases:
- When
initialCurrency
doesn't exist in either day's pairs - When there's no path back to
initialCurrency
on Day 2 - Empty pairs/rates arrays
The Solution: Add explicit handling for these cases:
def maxAmount(self, initialCurrency: str, pairs1: List[List[str]],
rates1: List[float], pairs2: List[List[str]], rates2: List[float]) -> float:
# Handle empty inputs
if not pairs1 or not pairs2:
return 1.0 # No exchanges possible, return initial amount
market1_rates = self._build_exchange_rates(pairs1, rates1, initialCurrency)
market2_rates = self._build_exchange_rates(pairs2, rates2, initialCurrency)
# If initial currency not reachable in market2, can't convert back
if initialCurrency not in market2_rates:
return 1.0
# Start with the option of no exchanges (keeping initial currency)
max_amount = 1.0
for currency, rate_from_initial in market2_rates.items():
if currency in market1_rates:
round_trip_value = market1_rates[currency] / rate_from_initial
max_amount = max(max_amount, round_trip_value)
return max_amount
Pitfall 4: Floating Point Precision Issues
The Problem: Multiple currency conversions can accumulate floating-point errors, especially with many exchanges or extreme rates.
The Solution: While not always necessary for interview problems, consider using decimal arithmetic for financial calculations or setting a reasonable precision threshold when comparing values.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Want a Structured Path to Master System Design Too? Donβt Miss This!