
3387: Maximize Amount After Two Days of Conversions
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
Approach 1: Brute Force
Calculate all possible conversion paths for both days and find the maximum amount.
Algorithm Steps
- Build graphs for Day 1 and Day 2 exchange rates
- Calculate all possible conversion paths for Day 1
- For each Day 1 result, calculate conversions for Day 2
- 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
- Build adjacency lists for Day 1 and Day 2
- Apply Floyd-Warshall to compute maximum rates between all currency pairs
- Calculate all reachable amounts on Day 1
- Use Day 2 rates to determine final maximum amount
Complexity Analysis
Time Complexity | Space Complexity |
---|---|
O(V^3) | O(V^2) |
#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
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 |
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.
Visualization
The Floyd-Warshall algorithm works by progressively improving estimates on the maximum exchange rate between currencies:
- Initialization: Direct exchange rates between connected currencies
- Iteration: For each intermediate currency k, update rates between i and j if i→k→j is better
- 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.