2731. Movement of Robots
Problem Description
You have robots standing on an infinite number line at initial positions given by the array nums
. Each robot will move when given a command based on the direction string s
:
'L'
means the robot moves left (negative direction) at 1 unit per second'R'
means the robot moves right (positive direction) at 1 unit per second
When two robots collide (occupy the same position), they instantly reverse their directions and continue moving. The collision happens instantaneously without any time delay.
Given:
nums
: initial positions of robots (0-indexed array)s
: string of directions wheres[i]
is the direction for roboti
d
: number of seconds to simulate
You need to find the sum of distances between all pairs of robots after d
seconds. Return the result modulo 10^9 + 7
.
Key insights about collisions:
- When robots collide, they swap directions immediately
- If robot at position 0 moving right meets robot at position 2 moving left:
- At t=1: both are at position 1 and collide
- After collision: first robot moves left, second robot moves right
- At t=2: first robot is at position 0, second robot is at position 2
Important notes:
- Each pair of robots is counted only once (pair
(i,j)
is the same as(j,i)
) - The sum can be very large, so return it modulo
10^9 + 7
The clever observation in the solution is that when two robots collide and swap directions, it's mathematically equivalent to them passing through each other without collision. This means we can simply calculate each robot's final position after d
seconds without worrying about collisions, then compute the sum of pairwise distances.
Intuition
The key insight is recognizing that when two robots collide and swap directions, the overall system behaves as if they simply passed through each other.
Consider two robots: Robot A at position 0 moving right, and Robot B at position 2 moving left. After 1 second, they meet at position 1 and collide. After the collision, Robot A moves left and Robot B moves right. After 2 seconds total, Robot A is back at position 0 and Robot B is at position 2.
Now imagine if they just passed through each other without colliding: Robot A would be at position 2 and Robot B would be at position 0 after 2 seconds. Notice that the set of final positions is the same {0, 2}
in both cases - only the robot identities have swapped!
This observation is crucial: for calculating the sum of pairwise distances, we don't care which specific robot is at which position. We only care about the set of final positions. The sum of distances between all pairs depends only on the positions themselves, not on which robot occupies which position.
Therefore, we can ignore collisions entirely and simply:
- Calculate where each robot would be after
d
seconds if there were no collisions - Sort these final positions
- Calculate the sum of pairwise distances
For the distance calculation, after sorting the positions, we can efficiently compute the sum by observing that for position nums[i]
, it contributes to the total sum by:
- Being subtracted
i
times (for all robots to its left) - Being added
(n-i-1)
times (for all robots to its right)
This can be simplified to: for each position x
at index i
, its contribution is i * x - sum_of_positions_before_i
.
Learn more about Prefix Sum and Sorting patterns.
Solution Approach
Based on our intuition that collisions can be ignored, the implementation follows these steps:
Step 1: Calculate Final Positions
for i, c in enumerate(s):
nums[i] += d if c == "R" else -d
For each robot at index i
, we update its position based on its direction:
- If moving right (
'R'
), addd
to its position - If moving left (
'L'
), subtractd
from its position
This gives us the final position of each robot after d
seconds, ignoring collisions.
Step 2: Sort the Positions
nums.sort()
We sort the final positions to prepare for efficient distance calculation. Sorting allows us to use a mathematical trick to compute the sum of pairwise distances in linear time.
Step 3: Calculate Sum of Pairwise Distances
ans = s = 0
for i, x in enumerate(nums):
ans += i * x - s
s += x
This is the clever part. For each position x
at index i
in the sorted array:
- The distance from
x
to all positions before it is:x - nums[0] + x - nums[1] + ... + x - nums[i-1]
- This simplifies to:
i * x - (nums[0] + nums[1] + ... + nums[i-1])
- We maintain a running sum
s
of all previous positions to avoid recalculating
The formula i * x - s
computes the total distance from position x
to all positions that come before it in the sorted array.
Step 4: Return Result Modulo
return ans % mod
Since the sum can be very large, we return the result modulo 10^9 + 7
.
Time Complexity: O(n log n)
due to sorting, where n
is the number of robots.
Space Complexity: O(1)
if we modify the input array in-place, or O(n)
if we need to preserve the original array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
nums = [1, 0]
(initial positions)s = "RL"
(Robot 0 moves Right, Robot 1 moves Left)d = 2
(simulate for 2 seconds)
Step 1: Calculate Final Positions (ignoring collisions)
For Robot 0 at position 1 moving Right ('R'):
- Final position = 1 + 2 = 3
For Robot 1 at position 0 moving Left ('L'):
- Final position = 0 - 2 = -2
After this step: nums = [3, -2]
Step 2: Sort the Positions
Sort the array: nums = [-2, 3]
Step 3: Calculate Sum of Pairwise Distances
We need to find the distance between all pairs. With sorted positions [-2, 3], there's only one pair:
- Distance between -2 and 3 = |3 - (-2)| = 5
Using our formula approach:
- Initialize:
ans = 0
,s = 0
- For index 0, position -2:
ans += 0 * (-2) - 0 = 0
s += -2
, sos = -2
- For index 1, position 3:
ans += 1 * 3 - (-2) = 3 + 2 = 5
s += 3
, sos = 1
Step 4: Return Result
ans = 5
, return 5 % (10^9 + 7) = 5
Verification with actual collisions: Let's verify this makes sense by tracking what actually happens with collisions:
- t=0: Robot 0 at position 1 (moving R), Robot 1 at position 0 (moving L)
- t=0.5: Both robots meet at position 0.5 and collide
- After collision: Robot 0 now moves L, Robot 1 now moves R
- t=2: Robot 0 at position -2, Robot 1 at position 3
The final positions are {-2, 3}, which matches our calculation! The distance between them is indeed 5.
This demonstrates that ignoring collisions and just calculating final positions gives us the correct set of positions for computing distances.
Solution Implementation
1class Solution:
2 def sumDistance(self, nums: List[int], s: str, d: int) -> int:
3 # Define modulo constant for large number handling
4 MOD = 10**9 + 7
5
6 # Update positions based on direction
7 # 'R' means move right (add d), 'L' means move left (subtract d)
8 for i, direction in enumerate(s):
9 if direction == "R":
10 nums[i] += d
11 else: # direction == "L"
12 nums[i] -= d
13
14 # Sort positions to calculate distances efficiently
15 nums.sort()
16
17 # Initialize variables for calculating sum of distances
18 total_distance = 0 # Final answer
19 prefix_sum = 0 # Running sum of positions
20
21 # Calculate sum of all pairwise distances
22 # For position at index i, its contribution is:
23 # i * nums[i] - sum of all previous positions
24 for i, position in enumerate(nums):
25 total_distance += i * position - prefix_sum
26 prefix_sum += position
27
28 # Return result with modulo applied
29 return total_distance % MOD
30
1class Solution {
2 public int sumDistance(int[] nums, String s, int d) {
3 int n = nums.length;
4 long[] finalPositions = new long[n];
5
6 // Calculate final position for each robot after d units of time
7 // Robots moving left subtract d, robots moving right add d
8 for (int i = 0; i < n; i++) {
9 if (s.charAt(i) == 'L') {
10 finalPositions[i] = (long) nums[i] - d;
11 } else {
12 finalPositions[i] = (long) nums[i] + d;
13 }
14 }
15
16 // Sort positions to efficiently calculate pairwise distances
17 Arrays.sort(finalPositions);
18
19 // Calculate sum of all pairwise distances using mathematical optimization
20 // For sorted array, distance contribution of position i is:
21 // i * finalPositions[i] - sum of all positions before i
22 long totalDistance = 0;
23 long prefixSum = 0;
24 final int MOD = 1000000007;
25
26 for (int i = 0; i < n; i++) {
27 // Current position contributes to distance with all previous positions
28 // Contribution = (number of previous positions) * current - sum of previous
29 totalDistance = (totalDistance + i * finalPositions[i] - prefixSum) % MOD;
30
31 // Update prefix sum for next iteration
32 prefixSum += finalPositions[i];
33 }
34
35 return (int) totalDistance;
36 }
37}
38
1class Solution {
2public:
3 int sumDistance(vector<int>& nums, string s, int d) {
4 int n = nums.size();
5
6 // Calculate final positions after d seconds
7 // Robots moving left subtract d, robots moving right add d
8 vector<long long> finalPositions(n);
9 for (int i = 0; i < n; ++i) {
10 if (s[i] == 'L') {
11 finalPositions[i] = static_cast<long long>(nums[i]) - d;
12 } else {
13 finalPositions[i] = static_cast<long long>(nums[i]) + d;
14 }
15 }
16
17 // Sort positions to efficiently calculate pairwise distances
18 sort(finalPositions.begin(), finalPositions.end());
19
20 // Calculate sum of all pairwise distances using prefix sum technique
21 // For position at index i, it contributes to the distance sum as:
22 // (i * finalPositions[i] - prefixSum) where prefixSum is sum of all positions before i
23 long long totalDistance = 0;
24 long long prefixSum = 0;
25 const int MOD = 1000000007;
26
27 for (int i = 0; i < n; ++i) {
28 // Current position contributes positive distance to all positions before it
29 // and negative distance from all positions after it
30 totalDistance = (totalDistance + i * finalPositions[i] - prefixSum) % MOD;
31 prefixSum += finalPositions[i];
32 }
33
34 return static_cast<int>(totalDistance);
35 }
36};
37
1/**
2 * Calculates the sum of distances between all pairs of robots after movement
3 * @param nums - Array of initial positions of robots
4 * @param s - String where s[i] is 'L' or 'R' indicating direction of robot i
5 * @param d - Distance each robot moves
6 * @returns The sum of distances between all pairs modulo 10^9 + 7
7 */
8function sumDistance(nums: number[], s: string, d: number): number {
9 const n: number = nums.length;
10
11 // Update each robot's position based on direction and distance
12 // Move left if 'L' (subtract d), move right if 'R' (add d)
13 for (let i: number = 0; i < n; ++i) {
14 nums[i] += s[i] === 'L' ? -d : d;
15 }
16
17 // Sort the final positions in ascending order
18 nums.sort((a: number, b: number) => a - b);
19
20 // Calculate sum of pairwise distances using mathematical optimization
21 let totalDistance: number = 0;
22 let prefixSum: number = 0;
23 const MOD: number = 1e9 + 7;
24
25 // For each position, calculate its contribution to total distance
26 // Position i contributes: i * nums[i] - sum of all previous positions
27 for (let i: number = 0; i < n; ++i) {
28 totalDistance = (totalDistance + i * nums[i] - prefixSum) % MOD;
29 prefixSum += nums[i];
30 }
31
32 return totalDistance;
33}
34
Time and Space Complexity
The time complexity of this algorithm is O(n log n)
, where n
is the number of robots (length of the nums
array).
Breaking down the operations:
- The first loop iterates through the string
s
and updates positions innums
:O(n)
- Sorting the
nums
array:O(n log n)
- The second loop calculates the sum of distances:
O(n)
- Overall time complexity:
O(n) + O(n log n) + O(n) = O(n log n)
The space complexity is O(n)
.
The analysis:
- The sorting operation typically requires
O(log n)
space for the recursion stack in algorithms like quicksort, orO(n)
space for algorithms like mergesort - Python's built-in
sort()
uses Timsort, which requiresO(n)
auxiliary space in the worst case - No additional data structures are created besides temporary variables
- Overall space complexity:
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow Before Applying Modulo
The Pitfall: The most common mistake is applying the modulo operation only at the very end. When calculating the sum of distances, intermediate values can exceed the maximum integer size in languages with fixed integer limits, causing overflow errors. Even in Python (which handles arbitrary precision integers), this can lead to performance issues with very large numbers.
Example of the Problem:
# WRONG - can cause overflow in other languages or performance issues
total_distance = 0
for i, position in enumerate(nums):
total_distance += i * position - prefix_sum
prefix_sum += position
return total_distance % MOD # Only applying modulo at the end
The Solution: Apply modulo operation during the accumulation to keep numbers manageable:
# CORRECT - apply modulo during calculation
total_distance = 0
prefix_sum = 0
for i, position in enumerate(nums):
total_distance = (total_distance + i * position - prefix_sum) % MOD
prefix_sum += position
return total_distance
2. Forgetting to Handle Negative Modulo Results
The Pitfall:
When subtracting prefix_sum
from the calculation, the intermediate result can become negative. In some programming languages, taking modulo of a negative number can produce unexpected results.
Example of the Problem:
# Potential issue with negative intermediate values total_distance += i * position - prefix_sum # This can be negative
The Solution: Ensure the result is always positive by adding MOD before taking modulo:
# SAFE - handles negative values correctly total_distance = (total_distance + i * position - prefix_sum + MOD) % MOD
Or alternatively:
# Also safe - separate addition and subtraction total_distance = (total_distance + i * position) % MOD total_distance = (total_distance - prefix_sum + MOD) % MOD
3. Modifying Input Array Without Consideration
The Pitfall:
The solution modifies the nums
array in-place when updating positions. This might not be acceptable if the original array needs to be preserved for other purposes or if the problem explicitly states not to modify the input.
The Solution: Create a copy of the array before modifications:
def sumDistance(self, nums: List[int], s: str, d: int) -> int:
MOD = 10**9 + 7
# Create a copy to preserve original array
positions = nums.copy() # or positions = list(nums)
# Now modify the copy instead
for i, direction in enumerate(s):
if direction == "R":
positions[i] += d
else:
positions[i] -= d
positions.sort()
# ... rest of the solution
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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!