2463. Minimum Total Distance Traveled
Problem Description
You have robots and factories positioned along the X-axis. Each robot starts at a specific position given in the array robot[i], and each factory is located at factory[j][0] with a repair capacity limit of factory[j][1] robots.
All robots are initially broken and need to be repaired. You can choose the initial movement direction (left or right) for each robot. When a robot reaches a factory that hasn't reached its repair limit, the factory repairs the robot and it stops moving.
The goal is to assign directions to all robots such that the total distance traveled by all robots is minimized.
Key points to understand:
- Each robot and factory has a unique position on the X-axis
- A robot can initially be at the same position as a factory
- All robots move at the same speed
- Robots don't collide - they pass through each other
- If a factory has reached its repair limit, robots pass through it without stopping
- The distance traveled by a robot moving from position
xto positionyis|y - x|
The solution uses dynamic programming with memoization. After sorting both robots and factories by position, it defines dfs(i, j) to find the minimum distance for assigning robots starting from index i to factories starting from index j.
For each factory j, there are two choices:
- Skip this factory:
dfs(i, j) = dfs(i, j+1) - Use this factory to repair
krobots (wherekranges from 0 to the factory's limit): Calculate the total distance for thesekrobots to reach factoryj, then adddfs(i+k+1, j+1)for the remaining robots and factories
The algorithm returns the minimum among all possible assignments, ensuring every robot gets repaired while minimizing the total distance traveled.
Intuition
The key insight is that once we sort both robots and factories by position, the problem becomes an assignment problem where we want to match robots to factories optimally.
Why sorting helps: After sorting, we can observe that it's never optimal for robot assignments to "cross over". If robot A is to the left of robot B, and factory X is to the left of factory Y, it wouldn't make sense for robot A to go to factory Y while robot B goes to factory X - they would travel unnecessary extra distance.
This observation leads us to think about the problem sequentially: for each group of robots, we decide which factory they should go to. This naturally suggests a dynamic programming approach where we process robots and factories in order.
The state we need to track is: "Starting from robot i and factory j, what's the minimum distance to repair all remaining robots?" This is our dfs(i, j) function.
For each factory, we face a decision: how many robots should this factory repair? Since factories have limits, we can't send all robots to one factory. We need to try different possibilities:
- Don't use this factory at all (skip to the next factory)
- Send 1 robot to this factory
- Send 2 robots to this factory
- ... up to the factory's limit
The greedy insight within this DP approach: if we decide factory j will repair k robots, it should repair the next k consecutive robots starting from position i. Why? Because these are the closest unassigned robots to this factory (remember, we sorted everything).
This combination of sorting + dynamic programming with memoization allows us to efficiently explore all valid assignments while avoiding redundant calculations, ultimately finding the optimal solution where the total distance is minimized.
Learn more about Dynamic Programming and Sorting patterns.
Solution Implementation
1class Solution: 2 def minimumTotalDistance(self, robot: List[int], factory: List[List[int]]) -> int: 3 """ 4 Calculate the minimum total distance to repair all robots using available factories. 5 6 Args: 7 robot: List of robot positions 8 factory: List of [position, capacity] for each factory 9 10 Returns: 11 Minimum total distance for all robots to reach factories 12 """ 13 14 @cache 15 def dp(robot_idx: int, factory_idx: int) -> int: 16 """ 17 Dynamic programming function to find minimum distance. 18 19 Args: 20 robot_idx: Current index of robot being considered 21 factory_idx: Current index of factory being considered 22 23 Returns: 24 Minimum distance to repair robots from robot_idx onwards 25 using factories from factory_idx onwards 26 """ 27 # Base case: All robots have been assigned 28 if robot_idx == len(robot): 29 return 0 30 31 # Base case: No more factories available but robots remain 32 if factory_idx == len(factory): 33 return float('inf') 34 35 # Option 1: Skip current factory 36 min_distance = dp(robot_idx, factory_idx + 1) 37 38 # Option 2: Use current factory for some robots 39 cumulative_distance = 0 40 factory_position = factory[factory_idx][0] 41 factory_capacity = factory[factory_idx][1] 42 43 # Try assigning 1 to capacity robots to current factory 44 for num_robots in range(factory_capacity): 45 # Check if we have enough robots remaining 46 if robot_idx + num_robots >= len(robot): 47 break 48 49 # Add distance for current robot to current factory 50 cumulative_distance += abs(robot[robot_idx + num_robots] - factory_position) 51 52 # Calculate total distance if we assign (num_robots + 1) robots to this factory 53 min_distance = min( 54 min_distance, 55 cumulative_distance + dp(robot_idx + num_robots + 1, factory_idx + 1) 56 ) 57 58 return min_distance 59 60 # Sort both lists to enable optimal assignment 61 robot.sort() 62 factory.sort() 63 64 # Calculate the minimum total distance 65 result = dp(0, 0) 66 67 # Clear the cache to free memory 68 dp.cache_clear() 69 70 return result 711class Solution { 2 // Memoization table: dp[i][j] represents minimum distance for robots starting from index i 3 // and factories starting from index j 4 private long[][] dp; 5 private List<Integer> robotPositions; 6 private int[][] factoryData; 7 8 public long minimumTotalDistance(List<Integer> robot, int[][] factory) { 9 // Sort robots and factories by position for optimal assignment 10 Collections.sort(robot); 11 Arrays.sort(factory, (a, b) -> Integer.compare(a[0], b[0])); 12 13 // Initialize instance variables 14 this.robotPositions = robot; 15 this.factoryData = factory; 16 this.dp = new long[robot.size()][factory.length]; 17 18 // Start DFS from first robot and first factory 19 return dfs(0, 0); 20 } 21 22 /** 23 * Recursively find minimum total distance to repair robots 24 * @param robotIndex current robot index to be assigned 25 * @param factoryIndex current factory index being considered 26 * @return minimum total distance for remaining robots 27 */ 28 private long dfs(int robotIndex, int factoryIndex) { 29 // Base case: all robots have been assigned 30 if (robotIndex == robotPositions.size()) { 31 return 0; 32 } 33 34 // Base case: no more factories available but robots remain 35 if (factoryIndex == factoryData.length) { 36 return Long.MAX_VALUE / 1000; // Return large value to indicate invalid path 37 } 38 39 // Check memoization table 40 if (dp[robotIndex][factoryIndex] != 0) { 41 return dp[robotIndex][factoryIndex]; 42 } 43 44 // Option 1: Skip current factory and try next one 45 long minDistance = dfs(robotIndex, factoryIndex + 1); 46 47 // Option 2: Assign robots to current factory 48 long currentDistance = 0; 49 int factoryCapacity = factoryData[factoryIndex][1]; 50 int factoryPosition = factoryData[factoryIndex][0]; 51 52 // Try assigning k robots to current factory (k from 1 to capacity) 53 for (int k = 0; k < factoryCapacity; k++) { 54 // Check if we have enough robots remaining 55 if (robotIndex + k >= robotPositions.size()) { 56 break; 57 } 58 59 // Add distance for current robot to factory 60 currentDistance += Math.abs(robotPositions.get(robotIndex + k) - factoryPosition); 61 62 // Recursively calculate minimum distance for remaining robots and factories 63 long remainingDistance = dfs(robotIndex + k + 1, factoryIndex + 1); 64 65 // Update minimum distance 66 minDistance = Math.min(minDistance, currentDistance + remainingDistance); 67 } 68 69 // Store result in memoization table 70 dp[robotIndex][factoryIndex] = minDistance; 71 return minDistance; 72 } 73} 741using ll = long long; 2 3class Solution { 4public: 5 long long minimumTotalDistance(vector<int>& robot, vector<vector<int>>& factory) { 6 // Sort robots and factories by position for optimal assignment 7 sort(robot.begin(), robot.end()); 8 sort(factory.begin(), factory.end()); 9 10 // dp[i][j] = minimum distance to repair robots from index i onwards using factories from index j onwards 11 vector<vector<ll>> dp(robot.size(), vector<ll>(factory.size())); 12 13 // DFS with memoization to find minimum total distance 14 function<ll(int, int)> dfs = [&](int robotIdx, int factoryIdx) -> ll { 15 // Base case: all robots have been assigned 16 if (robotIdx == robot.size()) { 17 return 0; 18 } 19 20 // Base case: no more factories available but robots remain 21 if (factoryIdx == factory.size()) { 22 return 1e15; // Return large value as impossible case 23 } 24 25 // Return memoized result if already computed 26 if (dp[robotIdx][factoryIdx]) { 27 return dp[robotIdx][factoryIdx]; 28 } 29 30 // Option 1: Skip current factory and use remaining factories 31 ll minDistance = dfs(robotIdx, factoryIdx + 1); 32 33 // Option 2: Use current factory for some robots 34 ll accumulatedDistance = 0; 35 int factoryCapacity = factory[factoryIdx][1]; 36 int factoryPosition = factory[factoryIdx][0]; 37 38 // Try assigning k robots to current factory (k from 1 to capacity) 39 for (int k = 0; k < factoryCapacity; ++k) { 40 // Check if we have enough robots remaining 41 if (robotIdx + k >= robot.size()) { 42 break; 43 } 44 45 // Add distance for current robot to current factory 46 accumulatedDistance += abs(robot[robotIdx + k] - factoryPosition); 47 48 // Calculate total distance if we assign (k+1) robots to current factory 49 // and process remaining robots with remaining factories 50 minDistance = min(minDistance, accumulatedDistance + dfs(robotIdx + k + 1, factoryIdx + 1)); 51 } 52 53 // Memoize and return the minimum distance 54 dp[robotIdx][factoryIdx] = minDistance; 55 return minDistance; 56 }; 57 58 // Start DFS from first robot and first factory 59 return dfs(0, 0); 60 } 61}; 621type ll = number; 2 3function minimumTotalDistance(robot: number[], factory: number[][]): number { 4 // Sort robots and factories by position for optimal assignment 5 robot.sort((a, b) => a - b); 6 factory.sort((a, b) => a[0] - b[0]); 7 8 // dp[i][j] = minimum distance to repair robots from index i onwards using factories from index j onwards 9 const dp: ll[][] = Array(robot.length).fill(null).map(() => Array(factory.length).fill(0)); 10 11 // DFS with memoization to find minimum total distance 12 const dfs = (robotIdx: number, factoryIdx: number): ll => { 13 // Base case: all robots have been assigned 14 if (robotIdx === robot.length) { 15 return 0; 16 } 17 18 // Base case: no more factories available but robots remain 19 if (factoryIdx === factory.length) { 20 return 1e15; // Return large value as impossible case 21 } 22 23 // Return memoized result if already computed 24 if (dp[robotIdx][factoryIdx] !== 0) { 25 return dp[robotIdx][factoryIdx]; 26 } 27 28 // Option 1: Skip current factory and use remaining factories 29 let minDistance: ll = dfs(robotIdx, factoryIdx + 1); 30 31 // Option 2: Use current factory for some robots 32 let accumulatedDistance: ll = 0; 33 const factoryCapacity: number = factory[factoryIdx][1]; 34 const factoryPosition: number = factory[factoryIdx][0]; 35 36 // Try assigning k robots to current factory (k from 1 to capacity) 37 for (let k = 0; k < factoryCapacity; k++) { 38 // Check if we have enough robots remaining 39 if (robotIdx + k >= robot.length) { 40 break; 41 } 42 43 // Add distance for current robot to current factory 44 accumulatedDistance += Math.abs(robot[robotIdx + k] - factoryPosition); 45 46 // Calculate total distance if we assign (k+1) robots to current factory 47 // and process remaining robots with remaining factories 48 minDistance = Math.min(minDistance, accumulatedDistance + dfs(robotIdx + k + 1, factoryIdx + 1)); 49 } 50 51 // Memoize and return the minimum distance 52 dp[robotIdx][factoryIdx] = minDistance; 53 return minDistance; 54 }; 55 56 // Start DFS from first robot and first factory 57 return dfs(0, 0); 58} 59Solution Approach
The solution implements a top-down dynamic programming approach with memoization. Here's how the implementation works:
Step 1: Sorting
robot.sort() factory.sort()
First, we sort both the robots and factories arrays in ascending order by position. This ensures we can process them sequentially and avoid crossing assignments.
Step 2: Define the DP State The function dfs(i, j) represents the minimum total distance needed to repair all robots starting from index i using factories starting from index j.
Step 3: Base Cases
if i == len(robot): return 0 if j == len(factory): return inf
- If
i == len(robot), all robots have been assigned, so the distance is 0 - If
j == len(factory), we've run out of factories but still have robots to repair, which is impossible, so we return infinity
Step 4: Recursive Transitions For each state (i, j), we have two main choices:
-
Skip the current factory:
ans = dfs(i, j + 1)Move to the next factory without using the current one.
-
Use the current factory for k robots:
t = 0 for k in range(factory[j][1]): if i + k == len(robot): break t += abs(robot[i + k] - factory[j][0]) ans = min(ans, t + dfs(i + k + 1, j + 1))- Try assigning 1, 2, ..., up to
limitrobots to factoryj - For each choice of
krobots, calculate the cumulative distancet - The distance for robot at index
i+kto reach factoryjis|robot[i+k] - factory[j][0]| - After assigning
k+1robots to this factory, recursively solve for the remaining robots starting fromi+k+1and the next factoryj+1
- Try assigning 1, 2, ..., up to
Step 5: Memoization The @cache decorator automatically memoizes the results of dfs(i, j), preventing redundant calculations for the same state.
Step 6: Return the Result
ans = dfs(0, 0) dfs.cache_clear() return ans
Start the recursion from the first robot and first factory, clear the cache to free memory, and return the minimum total distance.
The time complexity is O(m * n * limit) where m is the number of robots, n is the number of factories, and limit is the maximum capacity of any factory. The space complexity is O(m * n) for the memoization cache.
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 small example to illustrate the solution approach.
Example:
robot = [1, 6](2 robots at positions 1 and 6)factory = [[2, 1], [7, 1]](Factory at position 2 with capacity 1, Factory at position 7 with capacity 1)
Step 1: Sort arrays
robot = [1, 6](already sorted)factory = [[2, 1], [7, 1]](already sorted)
Step 2: Start DFS from dfs(0, 0)
We need to assign robots starting from index 0 to factories starting from index 0.
At dfs(0, 0): (Robot index 0, Factory index 0)
- Current robot at position 1, current factory at position 2 with capacity 1
- Option 1: Skip factory 0 →
dfs(0, 1) - Option 2: Send 1 robot to factory 0
- Distance = |1 - 2| = 1
- Continue with
dfs(1, 1) - Total for this option: 1 +
dfs(1, 1)
Let's explore Option 2 first:
At dfs(1, 1): (Robot index 1, Factory index 1)
- Current robot at position 6, current factory at position 7 with capacity 1
- Option 1: Skip factory 1 →
dfs(1, 2)→ returns ∞ (no more factories) - Option 2: Send 1 robot to factory 1
- Distance = |6 - 7| = 1
- Continue with
dfs(2, 2)→ returns 0 (all robots assigned) - Total for this option: 1 + 0 = 1
So dfs(1, 1) = 1
Back to dfs(0, 0):
- Option 2 gives us: 1 + 1 = 2
Now let's explore Option 1:
At dfs(0, 1): (Robot index 0, Factory index 1)
- Current robot at position 1, current factory at position 7 with capacity 1
- Option 1: Skip factory 1 →
dfs(0, 2)→ returns ∞ (no more factories) - Option 2: Send 1 robot to factory 1
- Distance = |1 - 7| = 6
- Continue with
dfs(1, 2)→ returns ∞ (robot at index 1 can't be assigned) - Total: ∞
So dfs(0, 1) = ∞
Final Result:
dfs(0, 0) = min(∞, 2) = 2
The optimal assignment is:
- Robot at position 1 → Factory at position 2 (distance = 1)
- Robot at position 6 → Factory at position 7 (distance = 1)
- Total distance = 2
This example shows how the algorithm explores different assignments, using memoization to avoid recalculating states, and ultimately finds the minimum total distance by trying all valid combinations of robot-to-factory assignments.
Time and Space Complexity
Time Complexity: O(m^2 × n)
The time complexity analysis breaks down as follows:
- The recursive function
dfs(i, j)has states defined by parametersi(robot index) andj(factory index), creatingm × nunique states wherem = len(robot)andn = len(factory). - Due to memoization with
@cache, each state is computed only once. - For each state
(i, j), the function iterates through up tofactory[j][1]positions to assign robots to the current factory. In the worst case, this can be up tomrobots (when a factory has capacity equal to or greater than the number of robots). - Within this loop, each iteration performs
O(1)operations (calculating distance and making recursive calls). - Therefore, the work done per state is
O(m), and withm × nstates, the total time complexity isO(m × n × m) = O(m^2 × n).
Space Complexity: O(m × n)
The space complexity consists of:
- The memoization cache stores up to
m × nstates (all possible combinations of robot and factory indices). - The recursion depth is at most
O(m + n)in the worst case (processing all robots and factories). - Since
m × ndominatesm + n, the overall space complexity isO(m × n).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Sort the Arrays
One of the most common mistakes is forgetting to sort both the robot and factory arrays before applying dynamic programming. Without sorting, the greedy assignment strategy breaks down because we might assign distant robots to nearby factories, leading to suboptimal solutions.
Why it matters: The DP solution relies on the fact that once sorted, we can assign robots to factories in order without worrying about crossing assignments (where a robot further left goes to a factory further right while a robot further right goes to a factory further left).
Solution:
# Always sort both arrays at the beginning robot.sort() factory.sort()
2. Off-by-One Error in the Loop
When iterating through the number of robots to assign to a factory, it's easy to make an off-by-one error:
Incorrect:
for num_robots in range(factory_capacity + 1): # Wrong! This tries to assign capacity+1 robots if robot_idx + num_robots >= len(robot): break
Correct:
for num_robots in range(factory_capacity): # Assigns 0 to capacity-1 robots (total of capacity iterations) if robot_idx + num_robots >= len(robot): break # When using num_robots as index offset, we're actually assigning num_robots+1 robots cumulative_distance += abs(robot[robot_idx + num_robots] - factory_position) min_distance = min(min_distance, cumulative_distance + dp(robot_idx + num_robots + 1, factory_idx + 1))
3. Not Handling the "Assign Zero Robots" Case
Some implementations forget that it's valid to not assign any robots to a factory (essentially skipping it). This is already handled by having two separate options in the code, but merging them incorrectly can cause issues:
Problematic approach:
# Starting from 1 instead of considering the skip option separately for num_robots in range(1, factory_capacity + 1): # Misses the case of assigning 0 robots ...
Correct approach:
# Option 1: Skip factory (assign 0 robots) min_distance = dp(robot_idx, factory_idx + 1) # Option 2: Assign 1 or more robots for num_robots in range(factory_capacity): ...
4. Integer Overflow with Large Values
When no valid assignment exists, returning a very large number is necessary. Using sys.maxsize or a large integer might cause overflow issues in some languages or when adding distances.
Solution:
# Use float('inf') for impossible cases if factory_idx == len(factory): return float('inf')
5. Forgetting to Clear the Cache
While not affecting correctness, forgetting to clear the cache can cause memory issues in systems where the same Solution instance is reused for multiple test cases:
Solution:
result = dp(0, 0) dp.cache_clear() # Important for memory management return result
What does the following code do?
1def f(arr1, arr2): 2 i, j = 0, 0 3 new_arr = [] 4 while i < len(arr1) and j < len(arr2): 5 if arr1[i] < arr2[j]: 6 new_arr.append(arr1[i]) 7 i += 1 8 else: 9 new_arr.append(arr2[j]) 10 j += 1 11 new_arr.extend(arr1[i:]) 12 new_arr.extend(arr2[j:]) 13 return new_arr 141public static List<Integer> f(int[] arr1, int[] arr2) { 2 int i = 0, j = 0; 3 List<Integer> newArr = new ArrayList<>(); 4 5 while (i < arr1.length && j < arr2.length) { 6 if (arr1[i] < arr2[j]) { 7 newArr.add(arr1[i]); 8 i++; 9 } else { 10 newArr.add(arr2[j]); 11 j++; 12 } 13 } 14 15 while (i < arr1.length) { 16 newArr.add(arr1[i]); 17 i++; 18 } 19 20 while (j < arr2.length) { 21 newArr.add(arr2[j]); 22 j++; 23 } 24 25 return newArr; 26} 271function f(arr1, arr2) { 2 let i = 0, j = 0; 3 let newArr = []; 4 5 while (i < arr1.length && j < arr2.length) { 6 if (arr1[i] < arr2[j]) { 7 newArr.push(arr1[i]); 8 i++; 9 } else { 10 newArr.push(arr2[j]); 11 j++; 12 } 13 } 14 15 while (i < arr1.length) { 16 newArr.push(arr1[i]); 17 i++; 18 } 19 20 while (j < arr2.length) { 21 newArr.push(arr2[j]); 22 j++; 23 } 24 25 return newArr; 26} 27Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!