Facebook Pixel

3528. Unit Conversion I

Problem Description

You have n different types of units, numbered from 0 to n - 1. You're given conversion relationships between these units in a 2D array called conversions with length n - 1.

Each element in conversions is formatted as [sourceUnit_i, targetUnit_i, conversionFactor_i], which means:

  • 1 unit of type sourceUnit_i equals conversionFactor_i units of type targetUnit_i

Your task is to find how many units of each type are equivalent to 1 unit of type 0 (the base unit).

Return an array baseUnitConversion of length n where:

  • baseUnitConversion[i] represents how many units of type i equal 1 unit of type 0
  • Since the numbers can be very large, return each value modulo 10^9 + 7

The problem guarantees that unit 0 can be converted to any other unit through a unique conversion path, forming a tree structure with unit 0 as the root. The conversion relationships create a connected graph where you can reach any unit from unit 0 through the given conversions.

For example, if unit 0 converts to unit 1 with factor 3, then 1 unit of type 0 equals 3 units of type 1, so baseUnitConversion[1] = 3.

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 units (nodes) and conversion relationships (edges) between them. Each conversion [sourceUnit, targetUnit, conversionFactor] represents a directed edge from sourceUnit to targetUnit.

Is it a tree?

  • Yes: The problem states that there are n units and n-1 conversion relationships, and unit 0 can reach any other unit through a unique conversion path. This describes a tree structure with unit 0 as the root.

DFS

  • Yes: Since we have identified this as a tree problem, DFS is the appropriate algorithm to traverse from the root (unit 0) to all other nodes, calculating the conversion factors along the way.

Conclusion: The flowchart suggests using DFS (Depth-First Search) for this problem. Starting from unit 0 with a conversion factor of 1, we traverse the tree using DFS, multiplying the conversion factors along each path to compute how many units of each type equal 1 unit of type 0.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Since we need to find how many units of each type equal 1 unit of type 0, we can think of this as finding the "distance" or "conversion factor" from the root (unit 0) to every other unit in the tree.

The key insight is that when we have a conversion like "1 unit of A equals 5 units of B", and we know that "1 unit of type 0 equals 3 units of A", then we can deduce that "1 unit of type 0 equals 3 Γ— 5 = 15 units of B". This multiplication property naturally suggests a traversal approach where we accumulate the conversion factors as we move through the tree.

Since the problem guarantees a tree structure (n units with n-1 edges and unique paths), we don't need to worry about cycles or multiple paths to the same unit. This makes DFS a perfect fit - we can start from unit 0 with a conversion factor of 1, and as we traverse to each child unit, we multiply the current conversion factor by the edge's conversion factor.

For example, if we're at unit s with accumulated conversion factor mul (meaning 1 unit of type 0 = mul units of type s), and there's an edge from s to t with conversion factor w, then we know that 1 unit of type 0 equals mul Γ— w units of type t.

The modulo operation (10^9 + 7) is applied during multiplication to prevent integer overflow, as the conversion factors can become very large when multiplied along a path.

Learn more about Depth-First Search, Breadth-First Search and Graph patterns.

Solution Approach

The implementation uses DFS with an adjacency list representation of the tree structure.

Building the Graph: First, we create an adjacency list g where g[i] stores all the units that unit i can convert to, along with their conversion factors. We iterate through the conversions array and for each conversion [s, t, w], we add (t, w) to g[s]. This creates a directed tree rooted at unit 0.

DFS Traversal: We define a recursive function dfs(s, mul) where:

  • s is the current unit we're visiting
  • mul is the accumulated conversion factor from unit 0 to unit s

The DFS function works as follows:

  1. Store the conversion factor for the current unit: ans[s] = mul
  2. For each child unit t with conversion factor w in g[s]:
    • Recursively call dfs(t, mul * w % mod)
    • The new conversion factor is mul * w because if 1 unit of type 0 = mul units of type s, and 1 unit of type s = w units of type t, then 1 unit of type 0 = mul * w units of type t

Initialization and Execution:

  • Set mod = 10^9 + 7 for modulo operations
  • Initialize n = len(conversions) + 1 (total number of units)
  • Create the adjacency list g with n empty lists
  • Initialize the answer array ans with zeros
  • Start DFS from unit 0 with initial conversion factor 1: dfs(0, 1)

The algorithm has a time complexity of O(n) since we visit each unit exactly once in the tree traversal, and space complexity of O(n) for the adjacency list and recursion stack.

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 small example with 4 units (0, 1, 2, 3) and the following conversions:

  • [0, 1, 2]: 1 unit of type 0 = 2 units of type 1
  • [0, 2, 3]: 1 unit of type 0 = 3 units of type 2
  • [1, 3, 5]: 1 unit of type 1 = 5 units of type 3

Step 1: Build the adjacency list

g[0] = [(1, 2), (2, 3)]  // unit 0 converts to units 1 and 2
g[1] = [(3, 5)]          // unit 1 converts to unit 3
g[2] = []                // unit 2 has no outgoing conversions
g[3] = []                // unit 3 has no outgoing conversions

Step 2: Start DFS from unit 0 with conversion factor 1

dfs(0, 1):

  • Set ans[0] = 1 (1 unit of type 0 = 1 unit of type 0)
  • Process children of unit 0:
    • Child 1 with factor 2: Call dfs(1, 1 * 2 = 2)
    • Child 2 with factor 3: Call dfs(2, 1 * 3 = 3)

dfs(1, 2):

  • Set ans[1] = 2 (1 unit of type 0 = 2 units of type 1)
  • Process children of unit 1:
    • Child 3 with factor 5: Call dfs(3, 2 * 5 = 10)

dfs(3, 10):

  • Set ans[3] = 10 (1 unit of type 0 = 10 units of type 3)
  • No children, return

dfs(2, 3):

  • Set ans[2] = 3 (1 unit of type 0 = 3 units of type 2)
  • No children, return

Final Result: ans = [1, 2, 3, 10]

This means:

  • 1 unit of type 0 = 1 unit of type 0
  • 1 unit of type 0 = 2 units of type 1
  • 1 unit of type 0 = 3 units of type 2
  • 1 unit of type 0 = 10 units of type 3

Note how unit 3 gets its conversion factor by multiplying along the path: 1 (starting) Γ— 2 (0β†’1) Γ— 5 (1β†’3) = 10.

Solution Implementation

1class Solution:
2    def baseUnitConversions(self, conversions: List[List[int]]) -> List[int]:
3        """
4        Calculate conversion factors from base unit (unit 0) to all other units.
5      
6        Args:
7            conversions: List of [source_unit, target_unit, conversion_factor] triplets
8      
9        Returns:
10            List where index i contains the conversion factor from unit 0 to unit i
11        """
12      
13        def dfs(current_unit: int, cumulative_factor: int) -> None:
14            """
15            Depth-first search to calculate conversion factors.
16          
17            Args:
18                current_unit: The unit we're currently processing
19                cumulative_factor: The accumulated conversion factor from unit 0
20            """
21            # Store the conversion factor for the current unit
22            conversion_factors[current_unit] = cumulative_factor
23          
24            # Process all units that can be reached from the current unit
25            for target_unit, conversion_ratio in adjacency_list[current_unit]:
26                # Calculate new cumulative factor with modulo to prevent overflow
27                new_factor = (cumulative_factor * conversion_ratio) % MOD
28                dfs(target_unit, new_factor)
29      
30        # Constants
31        MOD = 10**9 + 7  # Modulo value to prevent integer overflow
32        num_units = len(conversions) + 1  # Total number of units
33      
34        # Build adjacency list representation of the conversion graph
35        adjacency_list = [[] for _ in range(num_units)]
36        for source_unit, target_unit, conversion_ratio in conversions:
37            adjacency_list[source_unit].append((target_unit, conversion_ratio))
38      
39        # Initialize result array to store conversion factors
40        conversion_factors = [0] * num_units
41      
42        # Start DFS from unit 0 with initial conversion factor of 1
43        dfs(0, 1)
44      
45        return conversion_factors
46
1class Solution {
2    // Modulo value for preventing integer overflow
3    private static final int MOD = (int) 1e9 + 7;
4  
5    // Adjacency list representation of the conversion graph
6    // Each node stores a list of [targetUnit, conversionFactor] pairs
7    private List<int[]>[] adjacencyList;
8  
9    // Array to store the final conversion factors from base unit to each unit
10    private int[] conversionFactors;
11  
12    // Total number of units
13    private int numberOfUnits;
14
15    /**
16     * Calculates conversion factors from base unit (unit 0) to all other units.
17     * 
18     * @param conversions 2D array where each element [fromUnit, toUnit, factor] 
19     *                    represents that 1 fromUnit = factor * toUnit
20     * @return Array of conversion factors where index i contains the factor to convert 
21     *         from base unit to unit i
22     */
23    public int[] baseUnitConversions(int[][] conversions) {
24        // Total units = number of conversions + 1 (for the base unit)
25        numberOfUnits = conversions.length + 1;
26      
27        // Initialize the adjacency list for the graph
28        adjacencyList = new List[numberOfUnits];
29        Arrays.setAll(adjacencyList, index -> new ArrayList<>());
30      
31        // Initialize the result array
32        conversionFactors = new int[numberOfUnits];
33      
34        // Build the graph from conversion relationships
35        // For each conversion, add an edge from source unit to target unit with the conversion factor
36        for (int[] conversion : conversions) {
37            int fromUnit = conversion[0];
38            int toUnit = conversion[1];
39            int factor = conversion[2];
40            adjacencyList[fromUnit].add(new int[] {toUnit, factor});
41        }
42      
43        // Start DFS from base unit (unit 0) with initial conversion factor of 1
44        depthFirstSearch(0, 1);
45      
46        return conversionFactors;
47    }
48
49    /**
50     * Performs depth-first search to calculate conversion factors for all reachable units.
51     * 
52     * @param currentUnit The current unit being processed
53     * @param cumulativeFactor The cumulative conversion factor from base unit to current unit
54     */
55    private void depthFirstSearch(int currentUnit, long cumulativeFactor) {
56        // Store the conversion factor for the current unit
57        conversionFactors[currentUnit] = (int) cumulativeFactor;
58      
59        // Traverse all units that can be reached from the current unit
60        for (int[] edge : adjacencyList[currentUnit]) {
61            int targetUnit = edge[0];
62            int conversionFactor = edge[1];
63          
64            // Calculate new cumulative factor and continue DFS
65            // Use modulo to prevent overflow
66            long newCumulativeFactor = (cumulativeFactor * conversionFactor) % MOD;
67            depthFirstSearch(targetUnit, newCumulativeFactor);
68        }
69    }
70}
71
1class Solution {
2public:
3    vector<int> baseUnitConversions(vector<vector<int>>& conversions) {
4        const int MOD = 1e9 + 7;
5        int numUnits = conversions.size() + 1;  // Total number of units (including base unit 0)
6      
7        // Build adjacency list representation of the conversion graph
8        // graph[from] = list of pairs (to, conversionFactor)
9        vector<vector<pair<int, int>>> graph(numUnits);
10      
11        // Result array to store conversion factors from base unit to each unit
12        vector<int> result(numUnits);
13      
14        // Populate the graph with conversion relationships
15        for (const auto& conversion : conversions) {
16            int fromUnit = conversion[0];
17            int toUnit = conversion[1];
18            int conversionFactor = conversion[2];
19            graph[fromUnit].push_back({toUnit, conversionFactor});
20        }
21      
22        // DFS lambda function to calculate conversion factors
23        // Parameters: currentUnit - the unit we're currently processing
24        //            multiplier - accumulated conversion factor from base unit
25        auto dfs = [&](this auto&& dfs, int currentUnit, long long multiplier) -> void {
26            // Store the conversion factor for current unit
27            result[currentUnit] = multiplier;
28          
29            // Recursively process all units reachable from current unit
30            for (auto [nextUnit, conversionFactor] : graph[currentUnit]) {
31                // Calculate new multiplier and apply modulo to prevent overflow
32                dfs(nextUnit, multiplier * conversionFactor % MOD);
33            }
34        };
35      
36        // Start DFS from base unit (0) with initial multiplier of 1
37        dfs(0, 1);
38      
39        return result;
40    }
41};
42
1/**
2 * Calculates base unit conversions using DFS traversal
3 * @param conversions - Array of [source, target, weight] representing conversion relationships
4 * @returns Array of conversion factors for each unit relative to base unit 0
5 */
6function baseUnitConversions(conversions: number[][]): number[] {
7    const MOD = BigInt(1e9 + 7);
8    const nodeCount = conversions.length + 1;
9  
10    // Build adjacency list representation of the conversion graph
11    // Each node maps to an array of {target, weight} objects
12    const adjacencyList: { target: number; weight: number }[][] = Array.from(
13        { length: nodeCount }, 
14        () => []
15    );
16  
17    // Populate the adjacency list from conversions
18    for (const [source, target, weight] of conversions) {
19        adjacencyList[source].push({ target, weight });
20    }
21  
22    // Initialize result array to store conversion factors
23    const conversionFactors: number[] = Array(nodeCount).fill(0);
24  
25    /**
26     * Depth-first search to calculate conversion factors
27     * @param currentNode - Current node being processed
28     * @param currentMultiplier - Accumulated conversion factor from base unit
29     */
30    const performDFS = (currentNode: number, currentMultiplier: number): void => {
31        // Store the conversion factor for current node
32        conversionFactors[currentNode] = currentMultiplier;
33      
34        // Process all adjacent nodes
35        for (const { target, weight } of adjacencyList[currentNode]) {
36            // Calculate new multiplier with modulo to prevent overflow
37            const newMultiplier = Number((BigInt(currentMultiplier) * BigInt(weight)) % MOD);
38            performDFS(target, newMultiplier);
39        }
40    };
41  
42    // Start DFS from node 0 with multiplier 1
43    performDFS(0, 1);
44  
45    return conversionFactors;
46}
47

Time and Space Complexity

Time Complexity: O(n)

The algorithm performs a depth-first search (DFS) starting from node 0. Each node is visited exactly once during the DFS traversal. For each node visited, we iterate through its adjacent nodes in the graph. Since we're building the graph from the conversions list which has n-1 edges (where n is the total number of units), and each edge is processed once during graph construction and once during DFS traversal, the total time complexity is O(n).

Space Complexity: O(n)

The space complexity consists of:

  • The graph g which stores adjacency lists for n nodes: O(n) space
  • The answer array ans which stores results for n nodes: O(n) space
  • The recursive call stack for DFS which in the worst case (when the graph forms a chain) can go up to depth n: O(n) space

Therefore, the overall space complexity is O(n), where n is the number of units.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Apply Modulo During Multiplication

The Pitfall: One of the most common mistakes is forgetting to apply the modulo operation during the multiplication step in the DFS traversal. Developers often write:

# INCORRECT - can cause integer overflow
new_factor = cumulative_factor * conversion_ratio
dfs(target_unit, new_factor % MOD)

or even worse:

# INCORRECT - no modulo at all
new_factor = cumulative_factor * conversion_ratio
dfs(target_unit, new_factor)

Why This is Problematic:

  • Python handles arbitrarily large integers, but the multiplication can still create extremely large intermediate values that slow down computation
  • In other languages, this would cause integer overflow
  • The final result after all multiplications might exceed the maximum value before applying modulo

The Solution: Always apply modulo immediately after multiplication:

# CORRECT
new_factor = (cumulative_factor * conversion_ratio) % MOD
dfs(target_unit, new_factor)

2. Building a Bidirectional Graph Instead of a Directed Tree

The Pitfall: Since the problem mentions that units can be converted between each other, developers might mistakenly build a bidirectional graph:

# INCORRECT - creates cycles
for source_unit, target_unit, conversion_ratio in conversions:
    adjacency_list[source_unit].append((target_unit, conversion_ratio))
    adjacency_list[target_unit].append((source_unit, 1/conversion_ratio))  # Wrong!

Why This is Problematic:

  • Creates cycles in the graph, causing infinite recursion in DFS
  • The problem specifically states the structure forms a tree with unit 0 as root
  • The inverse conversion ratio (1/conversion_ratio) would be incorrect for the problem's requirements

The Solution: Build a directed tree following the exact conversion relationships given:

# CORRECT - directed tree from source to target only
for source_unit, target_unit, conversion_ratio in conversions:
    adjacency_list[source_unit].append((target_unit, conversion_ratio))

3. Incorrect Initialization of Base Unit

The Pitfall: Forgetting to set the correct initial conversion factor for unit 0:

# INCORRECT - unit 0 should have conversion factor 1
dfs(0, 0)  # Starting with 0 will make all conversions 0

Why This is Problematic:

  • Unit 0 is the base unit, so 1 unit of type 0 equals 1 unit of type 0
  • Starting with 0 would propagate 0 to all other units
  • The result would be an array of all zeros

The Solution: Always start the DFS with conversion factor 1 for the base unit:

# CORRECT
dfs(0, 1)  # 1 unit of type 0 = 1 unit of type 0
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?


Recommended Readings

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

Load More