Facebook Pixel

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 and rates1
  • Day 2: Arrays pairs2 and rates2

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:

  1. On Day 1, you can perform any number of currency conversions (including zero) using only the Day 1 rates
  2. On Day 2, you can perform any number of currency conversions (including zero) using only the Day 2 rates
  3. 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:

  1. Build a graph of all possible conversions and compute the maximum amount of each currency reachable from initialCurrency on Day 1
  2. Build another graph for Day 2 to find all possible paths back to initialCurrency
  3. 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 of initialCurrency 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:

  1. First DFS on Day 1's graph to find the maximum amount of each currency reachable from initialCurrency
  2. Second DFS on Day 2's graph to find conversion rates back to initialCurrency
  3. The answer is the maximum value obtained by trying all intermediate currencies as transition points between the two days
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. What's the maximum amount of each currency we can obtain starting from initialCurrency on Day 1?
  2. 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:

  1. Use DFS from initialCurrency on Day 1's graph to find the maximum amount of every reachable currency
  2. Use DFS from initialCurrency on Day 2's graph to find conversion rates from initialCurrency to all reachable currencies
  3. 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)
  4. 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):

  1. Build two dictionaries d1 and d2 that store the maximum amount of each currency reachable from initialCurrency on Day 1 and Day 2 respectively
  2. For each currency in d2, calculate the maximum amount of initialCurrency obtainable by using that currency as the intermediate: d1.get(a, 0) / r2
  3. 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:

  1. 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.

  2. 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 amount 1.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

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 amount r2 from initial
  • d1.get(a, 0) gives the amount of currency a after Day 1 (0 if not reachable)
  • d1.get(a, 0) / r2 converts back to initial currency: if we have X units of currency a and it takes r2 units of initial to get 1 unit of a on Day 2, then we can convert X units of a back to X/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 Evaluator

Example 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:

  1. Hold USD between days:
    • After Day 1: 1.0 USD
    • Converting back on Day 2: 1.0 / 1.0 = 1.0 USD
  2. 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)
  3. 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:

  1. Building graph g in the build function: O(E) where E is the number of exchange pairs, as we iterate through all pairs once and add bidirectional edges.

  2. DFS traversal in the build function: O(V + E) where V is the number of unique currencies and E is the number of edges. Each vertex is visited once (tracked by dictionary d), and we explore all edges from visited vertices.

  3. The build function is called twice - once for pairs1/rates1 and once for pairs2/rates2. If we denote V1, E1 for the first day and V2, E2 for the second day, this gives us O(V1 + E1) + O(V2 + E2).

  4. The final max computation iterates through all currencies in d2, which is O(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)

  1. Graph g in each build call: O(V + E) - stores adjacency list with all vertices and bidirectional edges.

  2. Dictionary d in each build call: O(V) - stores exchange rate for each reachable currency.

  3. DFS recursion stack: O(V) in worst case when the graph forms a chain.

  4. Since build is called twice, we have space usage of O(V1 + E1) and O(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:

  1. When initialCurrency doesn't exist in either day's pairs
  2. When there's no path back to initialCurrency on Day 2
  3. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More