Maximize Amount After Two Days of Conversions

3387: Maximize Amount After Two Days of Conversions

Difficulty: Medium
Topics: Graph Theory, Dynamic Programming, Floyd-Warshall
Companies: Google, Bloomberg, Stripe
Pro Tip: This problem requires understanding of graph algorithms and dynamic programming. The optimal solution using Floyd-Warshall runs in O(V^3) time where V is the number of currencies.

Problem Statement

You are given an initial currency and exchange rates for two consecutive days. The task is to maximize the amount of initial currency after performing any number of conversions on both days, using the respective day's exchange rates.

Example 1

Input:
initialCurrency = "EUR"
pairs1 = [["EUR", "USD"], ["USD", "JPY"]]
rates1 = [2.0, 3.0]
pairs2 = [["JPY", "USD"], ["USD", "CHF"], ["CHF", "EUR"]]
rates2 = [4.0, 5.0, 6.0]

Output: 720.00000

Example 2

Input:
initialCurrency = "NGN"
pairs1 = [["NGN", "EUR"]]
rates1 = [9.0]
pairs2 = [["NGN", "EUR"]]
rates2 = [6.0]

Output: 1.50000
Problem Link: View on LeetCode ↗

Approach 1: Brute Force

Calculate all possible conversion paths for both days and find the maximum amount.

Algorithm Steps

  1. Build graphs for Day 1 and Day 2 exchange rates
  2. Calculate all possible conversion paths for Day 1
  3. For each Day 1 result, calculate conversions for Day 2
  4. Track the maximum amount of initial currency

Complexity Analysis

Time Complexity Space Complexity
O(n^k) O(n)

Approach 2: Floyd-Warshall Algorithm (Optimal)

Use Floyd-Warshall to compute all-pairs maximum exchange rates for both days.

Algorithm Steps

  1. Build adjacency lists for Day 1 and Day 2
  2. Apply Floyd-Warshall to compute maximum rates between all currency pairs
  3. Calculate all reachable amounts on Day 1
  4. Use Day 2 rates to determine final maximum amount

Complexity Analysis

Time Complexity Space Complexity
O(V^3) O(V^2)
Floyd-Warshall Solution
#include <unordered_map>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

class Solution {
public:
    unordered_map<string, unordered_map<string, double>> buildGraph(vector<vector<string>>& pairs, vector<double>& rates) {
        unordered_map<string, unordered_map<string, double>> graph;
        for (int i = 0; i < pairs.size(); ++i) {
            string from = pairs[i][0], to = pairs[i][1];
            double rate = rates[i];
            graph[from][to] = rate;
            graph[to][from] = 1.0 / rate;
        }
        return graph;
    }

    unordered_map<string, unordered_map<string, double>> floydWarshall(unordered_map<string, unordered_map<string, double>>& graph) {
        vector<string> currencies;
        for (auto& pair : graph) currencies.push_back(pair.first);

        unordered_map<string, unordered_map<string, double>> dist;
        for (auto& from : currencies) {
            for (auto& to : currencies) {
                if (from == to) dist[from][to] = 1.0;
                else if (graph[from].count(to)) dist[from][to] = graph[from][to];
                else dist[from][to] = 0.0;
            }
        }

        for (auto& k : currencies) {
            for (auto& i : currencies) {
                for (auto& j : currencies) {
                    dist[i][j] = max(dist[i][j], dist[i][k] * dist[k][j]);
                }
            }
        }
        return dist;
    }

    double maxAmount(string initialCurrency, vector<vector<string>>& pairs1, vector<double>& rates1, vector<vector<string>>& pairs2, vector<double>& rates2) {
        auto graph1 = buildGraph(pairs1, rates1);
        auto graph2 = buildGraph(pairs2, rates2);
        auto day1Rates = floydWarshall(graph1);
        auto day2Rates = floydWarshall(graph2);

        unordered_map<string, double> day1Amounts;
        for (auto& currency : day1Rates) {
            if (day1Rates[initialCurrency].count(currency.first)) {
                day1Amounts[currency.first] = day1Rates[initialCurrency][currency.first];
            }
        }
        day1Amounts[initialCurrency] = 1.0;

        double maxAmount = 1.0;
        for (auto& [currency, amount] : day1Amounts) {
            if (day2Rates.count(currency)) {
                maxAmount = max(maxAmount, amount * day2Rates[currency][initialCurrency]);
            }
        }

        return maxAmount;
    }
};
import java.util.*;

class Solution {
    public double maxAmount(String initialCurrency, String[][] pairs1, double[] rates1, String[][] pairs2, double[] rates2) {
        Map<String, Map<String, Double>> graph1 = buildGraph(pairs1, rates1);
        Map<String, Map<String, Double>> graph2 = buildGraph(pairs2, rates2);
        
        Map<String, Map<String, Double>> day1Rates = floydWarshall(graph1);
        Map<String, Map<String, Double>> day2Rates = floydWarshall(graph2);
        
        Map<String, Double> day1Amounts = new HashMap<>();
        for (String currency : day1Rates.keySet()) {
            if (day1Rates.get(initialCurrency).containsKey(currency)) {
                day1Amounts.put(currency, day1Rates.get(initialCurrency).get(currency));
            }
        }
        day1Amounts.put(initialCurrency, 1.0);
        
        double maxAmount = 1.0;
        for (Map.Entry<String, Double> entry : day1Amounts.entrySet()) {
            String currency = entry.getKey();
            double amount = entry.getValue();
            if (day2Rates.containsKey(currency)) {
                maxAmount = Math.max(maxAmount, amount * day2Rates.get(currency).get(initialCurrency));
            }
        }
        
        return maxAmount;
    }
    
    private Map<String, Map<String, Double>> buildGraph(String[][] pairs, double[] rates) {
        Map<String, Map<String, Double>> graph = new HashMap<>();
        for (int i = 0; i < pairs.length; i++) {
            String from = pairs[i][0];
            String to = pairs[i][1];
            double rate = rates[i];
            
            graph.putIfAbsent(from, new HashMap<>());
            graph.putIfAbsent(to, new HashMap<>());
            
            graph.get(from).put(to, rate);
            graph.get(to).put(from, 1.0 / rate);
        }
        return graph;
    }
    
    private Map<String, Map<String, Double>> floydWarshall(Map<String, Map<String, Double>> graph) {
        List<String> currencies = new ArrayList<>(graph.keySet());
        Map<String, Map<String, Double>> dist = new HashMap<>();
        
        // Initialize distances
        for (String from : currencies) {
            dist.put(from, new HashMap<>());
            for (String to : currencies) {
                if (from.equals(to)) {
                    dist.get(from).put(to, 1.0);
                } else if (graph.get(from).containsKey(to)) {
                    dist.get(from).put(to, graph.get(from).get(to));
                } else {
                    dist.get(from).put(to, 0.0);
                }
            }
        }
        
        // Floyd-Warshall algorithm
        for (String k : currencies) {
            for (String i : currencies) {
                for (String j : currencies) {
                    double newDist = dist.get(i).get(k) * dist.get(k).get(j);
                    if (newDist > dist.get(i).get(j)) {
                        dist.get(i).put(j, newDist);
                    }
                }
            }
        }
        
        return dist;
    }
}
from collections import defaultdict

class Solution:
    def maxAmount(self, initialCurrency: str, pairs1: List[List[str]], rates1: List[float], 
                 pairs2: List[List[str]], rates2: List[float]) -> float:
        
        def build_graph(pairs, rates):
            graph = defaultdict(dict)
            for (from_curr, to_curr), rate in zip(pairs, rates):
                graph[from_curr][to_curr] = rate
                graph[to_curr][from_curr] = 1.0 / rate
            return graph
        
        def floyd_warshall(graph):
            currencies = list(graph.keys())
            dist = defaultdict(dict)
            
            # Initialize distances
            for from_curr in currencies:
                for to_curr in currencies:
                    if from_curr == to_curr:
                        dist[from_curr][to_curr] = 1.0
                    elif to_curr in graph[from_curr]:
                        dist[from_curr][to_curr] = graph[from_curr][to_curr]
                    else:
                        dist[from_curr][to_curr] = 0.0
            
            # Floyd-Warshall algorithm
            for k in currencies:
                for i in currencies:
                    for j in currencies:
                        dist[i][j] = max(dist[i][j], dist[i][k] * dist[k][j])
            
            return dist
        
        graph1 = build_graph(pairs1, rates1)
        graph2 = build_graph(pairs2, rates2)
        
        day1_rates = floyd_warshall(graph1)
        day2_rates = floyd_warshall(graph2)
        
        day1_amounts = {}
        for currency in day1_rates[initialCurrency]:
            day1_amounts[currency] = day1_rates[initialCurrency][currency]
        day1_amounts[initialCurrency] = 1.0
        
        max_amount = 1.0
        for currency, amount in day1_amounts.items():
            if currency in day2_rates:
                max_amount = max(max_amount, amount * day2_rates[currency][initialCurrency])
        
        return max_amount
Implementation Note: The Floyd-Warshall approach efficiently computes all possible conversion paths by dynamically updating the maximum exchange rates between all currency pairs. This avoids the exponential complexity of the brute force method.

Comparison of Approaches

Approach Time Complexity Space Complexity When to Use Pros Cons
Brute Force O(n^k) O(n) Small input sizes Simple to implement Exponential time complexity
Floyd-Warshall O(V^3) O(V^2) Optimal solution Efficient for all currency pairs Higher space complexity
Interview Tip: While explaining the Floyd-Warshall solution, emphasize how it efficiently computes all-pairs shortest paths (or in this case, maximum exchange rates) by dynamic programming, avoiding redundant calculations.

Edge Cases and Special Considerations

When implementing solutions for this problem, consider these edge cases:

1. No Conversion Possible

Input: initialCurrency = "USD", pairs1 = [["EUR", "GBP"]], rates1 = [1.2], 
pairs2 = [["JPY", "AUD"]], rates2 = [1.5]
Output: 1.0
Explanation: No path exists to convert USD to other currencies and back.

2. Single Currency

Input: initialCurrency = "USD", pairs1 = [], rates1 = [], pairs2 = [], rates2 = []
Output: 1.0
Explanation: No conversions possible, amount remains the same.

3. Circular Conversions

Input: initialCurrency = "USD", pairs1 = [["USD", "EUR"], ["EUR", "GBP"], ["GBP", "USD"]], 
rates1 = [2.0, 3.0, 4.0], pairs2 = [["USD", "EUR"], ["EUR", "GBP"], ["GBP", "USD"]], 
rates2 = [0.5, 0.333, 0.25]
Output: 1.0
Explanation: Circular conversions result in the original amount.
Important: Always test your solution with these edge cases to ensure correctness. The examples demonstrate how different conversion paths can affect the final amount.

Visualization

The Floyd-Warshall algorithm works by progressively improving estimates on the maximum exchange rate between currencies:

  1. Initialization: Direct exchange rates between connected currencies
  2. Iteration: For each intermediate currency k, update rates between i and j if i→k→j is better
  3. Result: Matrix of maximum exchange rates between all currency pairs

This dynamic programming approach efficiently captures all possible conversion paths.


Frequently Asked Questions

1. Why use Floyd-Warshall instead of Dijkstra's algorithm?

Floyd-Warshall computes all-pairs maximum rates in O(V^3) time, which is more efficient than running Dijkstra's (O(V^2)) for each currency. It's particularly suited when we need maximum rates between all pairs, not just from a single source.

2. How does the algorithm handle reciprocal rates?

The graph construction includes both directions with reciprocal rates (e.g., if USD→EUR = 2.0, then EUR→USD = 0.5). This ensures the conversion works both ways with mathematically correct rates.

3. What's the significance of initializing diagonal elements to 1.0?

The diagonal (currency to itself) is set to 1.0 because converting a currency to itself should yield the same amount (no change). This is crucial for the dynamic programming approach to work correctly.

4. Can negative exchange rates be handled?

This problem assumes positive exchange rates. Negative rates would require different algorithms (like Bellman-Ford for detecting negative cycles) and have different economic implications.

5. How would you modify this for more than two days?

For n days, you would apply the Floyd-Warshall algorithm sequentially for each day's rates, using the previous day's result as input to the next. The complexity would be O(nV^3).

6. What optimizations are possible for sparse graphs?

For sparse graphs (few currency pairs), Johnson's algorithm (O(V^2 log V + VE)) might be more efficient, though Floyd-Warshall is often preferred for its simplicity and small constant factors.

7. How does this compare to matrix exponentiation methods?

Matrix exponentiation can also solve all-pairs problems but is less intuitive for this specific case. Floyd-Warshall's dynamic programming approach is more straightforward for currency conversion scenarios.

8. What's the practical limit for the number of currencies?

With V=100 currencies, Floyd-Warshall's O(V^3) = 1,000,000 operations is manageable. For V=1000 (1 billion operations), you might need optimizations or parallel processing.

9. How would you handle floating-point precision issues?

For very small/large rates, use logarithms to convert multiplications to additions, reducing floating-point errors. Alternatively, use arbitrary-precision arithmetic libraries if precision is critical.

10. Can this be adapted for cryptocurrency arbitrage?

Yes, by looking for cycles where the product of exchange rates is >1 (profit). The same Floyd-Warshall approach can detect such opportunities by checking diagonal entries after log transformation.

Final Thoughts: This problem demonstrates how graph algorithms can model real-world financial scenarios. The Floyd-Warshall solution provides an elegant way to maximize currency conversions while avoiding exponential complexity.