2615. Sum of Distances
Problem Description
You are given a 0-indexed integer array nums
. Your task is to create a new array arr
of the same length as nums
, where each element arr[i]
represents a specific sum calculation.
For each position i
in the array:
- Find all positions
j
wherenums[j] == nums[i]
andj != i
(all other positions that have the same value as positioni
) - Calculate the sum of absolute differences
|i - j|
for all such positionsj
- Store this sum in
arr[i]
- If no other position has the same value as
nums[i]
, setarr[i] = 0
Example walkthrough:
If nums = [1, 3, 1, 1, 2]
:
- For
i = 0
(value is 1): Other positions with value 1 are at indices 2 and 3arr[0] = |0 - 2| + |0 - 3| = 2 + 3 = 5
- For
i = 1
(value is 3): No other position has value 3arr[1] = 0
- For
i = 2
(value is 1): Other positions with value 1 are at indices 0 and 3arr[2] = |2 - 0| + |2 - 3| = 2 + 1 = 3
- For
i = 3
(value is 1): Other positions with value 1 are at indices 0 and 2arr[3] = |3 - 0| + |3 - 2| = 3 + 1 = 4
- For
i = 4
(value is 2): No other position has value 2arr[4] = 0
The result would be arr = [5, 0, 3, 4, 0]
.
Return the array arr
.
Intuition
The naive approach would be to iterate through each position and find all other positions with the same value, calculating the sum of distances. However, this would be inefficient with O(n²) complexity.
The key insight is to group all indices by their values first. For each value that appears multiple times, we have a list of indices where it occurs. Now we need to efficiently calculate the sum of distances for each index in this group.
Consider a value that appears at indices [i₁, i₂, i₃, ..., iₖ]
. For any index iⱼ
in this list, we need to calculate:
- Sum of distances to all indices on its left:
(iⱼ - i₁) + (iⱼ - i₂) + ... + (iⱼ - iⱼ₋₁)
- Sum of distances to all indices on its right:
(iⱼ₊₁ - iⱼ) + (iⱼ₊₂ - iⱼ) + ... + (iₖ - iⱼ)
Instead of recalculating these sums from scratch for each position, we can use a sliding technique. As we move from one index to the next in our sorted list:
-
Initial calculation for the first index
i₁
: All other indices are to its right, so we calculate the sum of(i₂ - i₁) + (i₃ - i₁) + ... + (iₖ - i₁)
. -
Moving from index
iⱼ
toiⱼ₊₁
:- The left sum increases because all indices to the left are now further away by
(iⱼ₊₁ - iⱼ)
. There arej
indices to the left, so left sum increases by(iⱼ₊₁ - iⱼ) * j
. - The right sum decreases because all indices to the right are now closer by
(iⱼ₊₁ - iⱼ)
. There are(k - j - 1)
indices to the right, so right sum decreases by(iⱼ₊₁ - iⱼ) * (k - j - 1)
.
- The left sum increases because all indices to the left are now further away by
This transformation allows us to update the sums in O(1) time as we traverse through each group, making the overall solution O(n) time complexity after the initial grouping.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation follows the intuition by using a dictionary to group indices and a sliding technique to efficiently calculate distances:
-
Group indices by value: Use a
defaultdict(list)
to store all indices for each unique value innums
. After iterating through the array,d[x]
contains a list of all indices where valuex
appears. -
Initialize result array: Create
ans
array of the same length asnums
, initialized with zeros. -
Process each group of indices: For each list of indices in the dictionary:
- Initialize
left = 0
(sum of distances to indices on the left) - Calculate initial
right
for the first index:sum(idx) - len(idx) * idx[0]
- This represents the sum of distances from
idx[0]
to all other indices in the group - Mathematically:
(idx[1] - idx[0]) + (idx[2] - idx[0]) + ... = sum(idx) - len(idx) * idx[0]
- This represents the sum of distances from
- Initialize
-
Traverse through each index in the group: For position
i
in the group:- Set
ans[idx[i]] = left + right
(total distance is sum of left and right distances) - Update for the next iteration (if not at the last index):
- Update left: When moving from
idx[i]
toidx[i+1]
, alli+1
indices on the left become further by(idx[i+1] - idx[i])
, so:left += (idx[i+1] - idx[i]) * (i + 1)
- Update right: The remaining
len(idx) - i - 1
indices on the right become closer by the same distance, so:right -= (idx[i+1] - idx[i]) * (len(idx) - i - 1)
- Update left: When moving from
- Set
-
Return the result: After processing all groups, return the
ans
array.
Time Complexity: O(n) where n is the length of the input array. We iterate through the array once to build the dictionary and once more to calculate distances.
Space Complexity: O(n) for the dictionary and result array.
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 smaller example to illustrate the solution approach clearly.
Given nums = [2, 1, 2, 1]
:
Step 1: Group indices by value
- Value 1 appears at indices: [1, 3]
- Value 2 appears at indices: [0, 2]
Step 2: Process group with value 2 (indices [0, 2])
For index 0:
- Initialize:
left = 0
(no indices to the left) - Calculate
right = sum([0, 2]) - 2 * 0 = 2 - 0 = 2
- This represents the distance from index 0 to index 2: |0 - 2| = 2
- Set
ans[0] = left + right = 0 + 2 = 2
- Update for next iteration:
left = 0 + (2 - 0) * 1 = 2
(1 index now on the left, distance increased by 2)right = 2 - (2 - 0) * 1 = 0
(1 index on the right became closer by 2)
For index 2:
- Current:
left = 2
,right = 0
- Set
ans[2] = left + right = 2 + 0 = 2
Step 3: Process group with value 1 (indices [1, 3])
For index 1:
- Initialize:
left = 0
- Calculate
right = sum([1, 3]) - 2 * 1 = 4 - 2 = 2
- This represents the distance from index 1 to index 3: |1 - 3| = 2
- Set
ans[1] = left + right = 0 + 2 = 2
- Update for next iteration:
left = 0 + (3 - 1) * 1 = 2
right = 2 - (3 - 1) * 1 = 0
For index 3:
- Current:
left = 2
,right = 0
- Set
ans[3] = left + right = 2 + 0 = 2
Final Result: ans = [2, 2, 2, 2]
Each element correctly represents the sum of distances to all other positions with the same value. The sliding technique allows us to calculate these sums efficiently without recalculating from scratch for each position.
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4class Solution:
5 def distance(self, nums: List[int]) -> List[int]:
6 # Group indices by their corresponding values in nums
7 value_to_indices = defaultdict(list)
8 for index, value in enumerate(nums):
9 value_to_indices[value].append(index)
10
11 # Initialize result array with zeros
12 result = [0] * len(nums)
13
14 # Process each group of indices that have the same value
15 for indices in value_to_indices.values():
16 # Initialize sum of distances to the left and right of first element
17 # left_sum: sum of distances from current position to all indices on the left
18 # right_sum: sum of distances from current position to all indices on the right
19 left_sum = 0
20 right_sum = sum(indices) - len(indices) * indices[0]
21
22 # Calculate distance sum for each index in this group
23 for i in range(len(indices)):
24 # Total distance is sum of left distances and right distances
25 result[indices[i]] = left_sum + right_sum
26
27 # Update left_sum and right_sum for next iteration
28 if i + 1 < len(indices):
29 # Gap between current and next index
30 gap = indices[i + 1] - indices[i]
31 # All (i + 1) indices on the left will contribute this gap
32 left_sum += gap * (i + 1)
33 # All remaining indices on the right will reduce by this gap
34 right_sum -= gap * (len(indices) - i - 1)
35
36 return result
37
1class Solution {
2 public long[] distance(int[] nums) {
3 int n = nums.length;
4 long[] result = new long[n];
5
6 // Group indices by their values
7 Map<Integer, List<Integer>> valueToIndices = new HashMap<>();
8 for (int i = 0; i < n; i++) {
9 valueToIndices.computeIfAbsent(nums[i], k -> new ArrayList<>()).add(i);
10 }
11
12 // Calculate sum of distances for each group of same values
13 for (List<Integer> indices : valueToIndices.values()) {
14 int groupSize = indices.size();
15
16 // Initialize left sum (sum of distances to elements on the left)
17 long leftSum = 0;
18
19 // Initialize right sum (sum of distances to elements on the right)
20 // Initially, all elements are on the right of index 0
21 long rightSum = -1L * groupSize * indices.get(0);
22 for (int index : indices) {
23 rightSum += index;
24 }
25
26 // Process each position in the group
27 for (int i = 0; i < groupSize; i++) {
28 // Total distance for current position is sum of left and right distances
29 result[indices.get(i)] = leftSum + rightSum;
30
31 // Update left and right sums for next iteration
32 if (i + 1 < groupSize) {
33 int currentIndex = indices.get(i);
34 int nextIndex = indices.get(i + 1);
35 int gap = nextIndex - currentIndex;
36
37 // Moving from position i to i+1:
38 // - Elements 0 to i are now on the left, distance increases by gap * (i+1)
39 leftSum += gap * (i + 1L);
40
41 // - Elements i+1 to m-1 are on the right, distance decreases by gap * (m-i-1)
42 rightSum -= gap * (groupSize - i - 1L);
43 }
44 }
45 }
46
47 return result;
48 }
49}
50
1class Solution {
2public:
3 vector<long long> distance(vector<int>& nums) {
4 int n = nums.size();
5 vector<long long> result(n);
6
7 // Group indices by their values
8 unordered_map<int, vector<int>> valueToIndices;
9 for (int i = 0; i < n; ++i) {
10 valueToIndices[nums[i]].push_back(i);
11 }
12
13 // Process each group of same values
14 for (auto& [value, indices] : valueToIndices) {
15 int groupSize = indices.size();
16
17 // Initialize left and right sums for the first element in the group
18 // leftSum: sum of distances to all elements on the left
19 long long leftSum = 0;
20 // rightSum: sum of distances to all elements on the right
21 // Initially, all elements are on the right of index 0
22 long long rightSum = -1LL * groupSize * indices[0];
23 for (int index : indices) {
24 rightSum += index;
25 }
26
27 // Calculate distance sum for each element in the group
28 for (int i = 0; i < groupSize; ++i) {
29 // Total distance is sum of left distances and right distances
30 result[indices[i]] = leftSum + rightSum;
31
32 // Update left and right sums for the next element
33 if (i + 1 < groupSize) {
34 int gap = indices[i + 1] - indices[i];
35 // Elements on the left (including current) will increase distance by gap
36 leftSum += gap * (i + 1);
37 // Elements on the right will decrease distance by gap
38 rightSum -= gap * (groupSize - i - 1);
39 }
40 }
41 }
42
43 return result;
44 }
45};
46
1function distance(nums: number[]): number[] {
2 const n = nums.length;
3 const result: number[] = new Array(n).fill(0);
4
5 // Group indices by their values
6 const valueToIndices = new Map<number, number[]>();
7 for (let i = 0; i < n; i++) {
8 if (!valueToIndices.has(nums[i])) {
9 valueToIndices.set(nums[i], []);
10 }
11 valueToIndices.get(nums[i])!.push(i);
12 }
13
14 // Process each group of same values
15 for (const [value, indices] of valueToIndices.entries()) {
16 const groupSize = indices.length;
17
18 // Initialize left and right sums for the first element in the group
19 // leftSum: sum of distances from current position to all elements on its left
20 let leftSum = 0;
21 // rightSum: sum of distances from current position to all elements on its right
22 // Initially, all other elements are on the right of the first element
23 let rightSum = -groupSize * indices[0];
24 for (const index of indices) {
25 rightSum += index;
26 }
27
28 // Calculate distance sum for each element in the group
29 for (let i = 0; i < groupSize; i++) {
30 // Total distance is the sum of distances to left elements and right elements
31 result[indices[i]] = leftSum + rightSum;
32
33 // Update left and right sums for the next element
34 if (i + 1 < groupSize) {
35 const gap = indices[i + 1] - indices[i];
36 // When moving to next element, all left elements (including current)
37 // will have their distances increased by the gap
38 leftSum += gap * (i + 1);
39 // All right elements will have their distances decreased by the gap
40 rightSum -= gap * (groupSize - i - 1);
41 }
42 }
43 }
44
45 return result;
46}
47
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input array nums
.
- Creating the dictionary
d
with indices grouped by values takesO(n)
time as we iterate through the array once. - For the main computation, we iterate through each group of indices in
d.values()
. - For each group of indices with length
k
, we perform:- Initial calculation of
right
:O(k)
time for the sum - Iteration through the group:
O(k)
operations
- Initial calculation of
- Since the total number of indices across all groups equals
n
, the overall time complexity isO(n)
.
Space Complexity: O(n)
- The dictionary
d
stores all indices, which requiresO(n)
space in the worst case. - The answer array
ans
requiresO(n)
space. - Other variables (
left
,right
, loop variables) useO(1)
space. - Therefore, the total space complexity is
O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Distance Calculation for Groups
A common mistake is trying to calculate distances naively by iterating through all pairs, leading to O(n²) complexity for groups with many duplicate values.
Pitfall Example:
# Inefficient approach that times out on large inputs
for i in range(len(nums)):
total = 0
for j in range(len(nums)):
if i != j and nums[i] == nums[j]:
total += abs(i - j)
result[i] = total
Solution: Use the sliding window technique shown in the optimal solution, maintaining running sums of left and right distances.
2. Off-by-One Errors in Index Counting
When updating left_sum
and right_sum
, it's easy to miscount how many indices are on each side.
Pitfall Example:
# Wrong: Using i instead of (i + 1) for left count
left_sum += gap * i # Should be (i + 1)
# Wrong: Using (len(indices) - i) instead of (len(indices) - i - 1)
right_sum -= gap * (len(indices) - i) # Should be (len(indices) - i - 1)
Solution: Remember that:
- After processing index
i
, there are exactly(i + 1)
indices on the left (including all previous ones) - There are
(len(indices) - i - 1)
indices remaining on the right (excluding current)
3. Incorrect Initial Right Sum Calculation
The initial right_sum
calculation can be confusing and prone to errors.
Pitfall Example:
# Wrong: Forgetting to multiply by the first index position
right_sum = sum(indices) - len(indices) # Missing multiplication by indices[0]
# Wrong: Using wrong formula
right_sum = sum(indices[1:]) - indices[0] * (len(indices) - 1) # Incorrect
Solution: The correct formula is sum(indices) - len(indices) * indices[0]
, which represents the sum of all distances from indices[0]
to every other index in the group.
4. Not Handling Single-Element Groups
While the code handles this correctly (left_sum = 0, right_sum = 0), it's easy to overlook that groups with only one element should result in 0.
Solution: The algorithm naturally handles this case since the loop runs once with both left_sum
and right_sum
as 0, but be mindful when modifying the code.
How many times is a tree node visited in a depth first search?
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
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!