2121. Intervals Between Identical Elements
Problem Description
You have an array arr
of n
integers indexed from 0 to n-1. For each element in the array, you need to calculate the sum of distances (intervals) between that element and all other elements in the array that have the same value.
The distance (interval) between two elements at positions i
and j
is defined as the absolute difference of their indices: |i - j|
.
Your task is to return an array intervals
where intervals[i]
contains the sum of all distances from arr[i]
to every other element in arr
that has the same value as arr[i]
.
For example:
- If
arr = [2, 1, 3, 1, 2, 3, 3]
- For the element at index 0 (value 2), you need to find all other positions with value 2 (which is index 4), and calculate
|0 - 4| = 4
- For the element at index 1 (value 1), you need to find all other positions with value 1 (which is index 3), and calculate
|1 - 3| = 2
- For the element at index 2 (value 3), you need to find all other positions with value 3 (indices 5 and 6), and calculate
|2 - 5| + |2 - 6| = 3 + 4 = 7
The solution groups indices by their values using a dictionary, then efficiently calculates the sum of intervals for each group. For each group of identical values, it uses a mathematical approach to compute the sum of distances by maintaining a running sum (val
) and updating it based on the position changes, avoiding the need to recalculate all distances for each element.
Intuition
The naive approach would be to iterate through each element and find all other elements with the same value, calculating the distance to each one. This would take O(n²) time, which is inefficient for large arrays.
The key insight is that we can group all indices by their values first. For elements with the same value, we need to calculate the sum of distances efficiently without recalculating everything from scratch for each position.
Consider elements with the same value at positions [p₀, p₁, p₂, ..., pₘ₋₁]
. For the element at position pᵢ
, the sum of distances is:
|pᵢ - p₀| + |pᵢ - p₁| + ... + |pᵢ - pₘ₋₁|
Since the positions are sorted, for element at pᵢ
:
- Elements to the left (
p₀
topᵢ₋₁
) contribute:(pᵢ - p₀) + (pᵢ - p₁) + ... + (pᵢ - pᵢ₋₁) = i * pᵢ - (p₀ + p₁ + ... + pᵢ₋₁)
- Elements to the right (
pᵢ₊₁
topₘ₋₁
) contribute:(pᵢ₊₁ - pᵢ) + (pᵢ₊₂ - pᵢ) + ... + (pₘ₋₁ - pᵢ) = (pᵢ₊₁ + ... + pₘ₋₁) - (m - i - 1) * pᵢ
The brilliant optimization comes from recognizing that when we move from position pᵢ₋₁
to pᵢ
, we can update the sum incrementally. The difference delta = pᵢ - pᵢ₋₁
affects the sum in a predictable way:
- The
i
elements to the left now have their distances increased bydelta
each, contributing+i * delta
- The
(m - i)
elements to the right (including current) have their distances decreased bydelta
each, contributing-(m - i) * delta
This allows us to maintain a running sum val
and update it in O(1) time for each element, reducing the overall complexity to O(n) after the initial grouping.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation uses a dictionary to group indices by their values, then applies the mathematical optimization discussed in the intuition.
Step 1: Group indices by values
d = defaultdict(list)
for i, v in enumerate(arr):
d[v].append(i)
We create a dictionary where each key is a value from the array, and the corresponding value is a list of all indices where that value appears.
Step 2: Initialize the result array
ans = [0] * n
Step 3: Process each group of identical values
For each group of indices v
in the dictionary:
m = len(v) # number of elements with this value
val = sum(v) - v[0] * m # initial sum for the first element
The initial val
for the first position v[0]
is calculated as:
sum(v)
gives us the sum of all positionsv[0] * m
is subtracted because for the first element, all distances are(v[1] - v[0]) + (v[2] - v[0]) + ... + (v[m-1] - v[0])
- This simplifies to
(v[1] + v[2] + ... + v[m-1]) - (m-1) * v[0]
- Which equals
sum(v) - v[0] * m
Step 4: Calculate sums for remaining positions in the group
for i, p in enumerate(v):
delta = v[i] - v[i - 1] if i >= 1 else 0
val += i * delta - (m - i) * delta
ans[p] = val
For each position after the first:
- Calculate
delta
: the distance moved from the previous position - Update
val
using the formula:val += i * delta - (m - i) * delta
i * delta
: increase from thei
elements now to our left(m - i) * delta
: decrease from the(m - i)
elements to our right (including current)
- Store the computed sum at the appropriate index in the answer array
Time Complexity: O(n) - we visit each element once for grouping and once for calculation Space Complexity: O(n) - for the dictionary storing the grouped indices and the answer 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 small example with arr = [2, 1, 3, 1, 2]
.
Step 1: Group indices by values
- Value 1 appears at indices [1, 3]
- Value 2 appears at indices [0, 4]
- Value 3 appears at index [2]
Our dictionary d
becomes:
{ 1: [1, 3], 2: [0, 4], 3: [2] }
Step 2: Process each group
Group with value 1: indices [1, 3]
m = 2
(two elements with value 1)- Initial sum for index 1:
val = sum([1, 3]) - 1 * 2 = 4 - 2 = 2
- This represents the distance from index 1 to index 3: |1 - 3| = 2
ans[1] = 2
For index 3:
delta = 3 - 1 = 2
(moved 2 positions from previous)val = 2 + 1 * 2 - (2 - 1) * 2 = 2 + 2 - 2 = 2
- The 1 element to the left (index 1) is now 2 units farther: +2
- The 1 element at/right (current position) is 2 units closer: -2
ans[3] = 2
Group with value 2: indices [0, 4]
m = 2
(two elements with value 2)- Initial sum for index 0:
val = sum([0, 4]) - 0 * 2 = 4 - 0 = 4
- This represents the distance from index 0 to index 4: |0 - 4| = 4
ans[0] = 4
For index 4:
delta = 4 - 0 = 4
(moved 4 positions from previous)val = 4 + 1 * 4 - (2 - 1) * 4 = 4 + 4 - 4 = 4
- The 1 element to the left (index 0) is now 4 units farther: +4
- The 1 element at/right (current position) is 4 units closer: -4
ans[4] = 4
Group with value 3: indices [2]
m = 1
(only one element with value 3)- Initial sum for index 2:
val = sum([2]) - 2 * 1 = 2 - 2 = 0
- No other elements with value 3, so sum is 0
ans[2] = 0
Final Result: ans = [4, 2, 0, 2, 4]
We can verify:
ans[0]
: distance from arr[0]=2 to arr[4]=2 is |0-4| = 4 ✓ans[1]
: distance from arr[1]=1 to arr[3]=1 is |1-3| = 2 ✓ans[2]
: no other 3s in the array, sum = 0 ✓ans[3]
: distance from arr[3]=1 to arr[1]=1 is |3-1| = 2 ✓ans[4]
: distance from arr[4]=2 to arr[0]=2 is |4-0| = 4 ✓
Solution Implementation
1class Solution:
2 def getDistances(self, arr: List[int]) -> List[int]:
3 # Group indices by their corresponding values in the array
4 value_to_indices = defaultdict(list)
5 n = len(arr)
6
7 # Build the mapping of value -> list of indices where it appears
8 for index, value in enumerate(arr):
9 value_to_indices[value].append(index)
10
11 # Initialize result array with zeros
12 result = [0] * n
13
14 # Process each group of indices that have the same value
15 for indices in value_to_indices.values():
16 count = len(indices)
17
18 # Calculate initial sum of distances for the first index in this group
19 # This is the sum of all indices minus the first index multiplied by count
20 current_sum = sum(indices) - indices[0] * count
21
22 # Calculate sum of distances for each index in the group
23 for i, current_index in enumerate(indices):
24 if i >= 1:
25 # Calculate the difference between current and previous index
26 index_diff = indices[i] - indices[i - 1]
27
28 # Update the sum using the formula:
29 # Elements before current index get closer by index_diff (i elements)
30 # Elements after current index get farther by index_diff (count - i elements)
31 current_sum += i * index_diff - (count - i) * index_diff
32
33 # Store the calculated sum for current index
34 result[current_index] = current_sum
35
36 return result
37
1class Solution {
2 public long[] getDistances(int[] arr) {
3 // Group indices by their corresponding values in the array
4 Map<Integer, List<Integer>> valueToIndicesMap = new HashMap<>();
5 int arrayLength = arr.length;
6
7 // Build the map: value -> list of indices where this value appears
8 for (int index = 0; index < arrayLength; ++index) {
9 valueToIndicesMap.computeIfAbsent(arr[index], k -> new ArrayList<>()).add(index);
10 }
11
12 // Initialize result array to store distances for each position
13 long[] result = new long[arrayLength];
14
15 // Process each group of indices that have the same value
16 for (List<Integer> indicesList : valueToIndicesMap.values()) {
17 int groupSize = indicesList.size();
18
19 // Calculate initial sum of distances for the first element in this group
20 // This is the sum of all indices minus (groupSize * firstIndex)
21 long currentSum = 0;
22 for (int index : indicesList) {
23 currentSum += index;
24 }
25 currentSum -= ((long) groupSize * indicesList.get(0));
26
27 // Calculate distances for each position in the group using sliding window technique
28 for (int i = 0; i < indicesList.size(); ++i) {
29 // Calculate the difference between current and previous index
30 int indexDifference = (i >= 1) ? indicesList.get(i) - indicesList.get(i - 1) : 0;
31
32 // Update sum using the formula:
33 // Elements to the left increase by indexDifference
34 // Elements to the right decrease by indexDifference
35 currentSum += (long) i * indexDifference - (long) (groupSize - i) * indexDifference;
36
37 // Store the calculated sum for the current position
38 result[indicesList.get(i)] = currentSum;
39 }
40 }
41
42 return result;
43 }
44}
45
1class Solution {
2public:
3 vector<long long> getDistances(vector<int>& arr) {
4 // Map to store indices of each unique value in the array
5 unordered_map<int, vector<int>> valueToIndices;
6 int n = arr.size();
7
8 // Group indices by their corresponding values
9 for (int i = 0; i < n; ++i) {
10 valueToIndices[arr[i]].push_back(i);
11 }
12
13 // Result array to store the sum of distances for each position
14 vector<long long> result(n);
15
16 // Process each group of same values
17 for (auto& [value, indices] : valueToIndices) {
18 int groupSize = indices.size();
19
20 // Calculate initial sum of distances for the first element in this group
21 // Sum of all indices minus (group size * first index)
22 long long currentSum = 0;
23 for (int index : indices) {
24 currentSum += index;
25 }
26 currentSum -= static_cast<long long>(groupSize) * indices[0];
27
28 // Calculate sum of distances for each position in this group
29 for (int i = 0; i < groupSize; ++i) {
30 // Calculate the difference from previous index (0 for first element)
31 int indexDiff = (i >= 1) ? indices[i] - indices[i - 1] : 0;
32
33 // Update the sum using the formula:
34 // Elements before current position contribute positive difference
35 // Elements after current position contribute negative difference
36 currentSum += static_cast<long long>(i) * indexDiff -
37 static_cast<long long>(groupSize - i) * indexDiff;
38
39 // Store the result at the corresponding position
40 result[indices[i]] = currentSum;
41 }
42 }
43
44 return result;
45 }
46};
47
1function getDistances(arr: number[]): number[] {
2 // Map to store indices of each unique value in the array
3 const valueToIndices: Map<number, number[]> = new Map();
4 const n: number = arr.length;
5
6 // Group indices by their corresponding values
7 for (let i = 0; i < n; i++) {
8 if (!valueToIndices.has(arr[i])) {
9 valueToIndices.set(arr[i], []);
10 }
11 valueToIndices.get(arr[i])!.push(i);
12 }
13
14 // Result array to store the sum of distances for each position
15 const result: number[] = new Array(n).fill(0);
16
17 // Process each group of same values
18 for (const [value, indices] of valueToIndices.entries()) {
19 const groupSize: number = indices.length;
20
21 // Calculate initial sum of distances for the first element in this group
22 // Sum of all indices minus (group size * first index)
23 let currentSum: number = 0;
24 for (const index of indices) {
25 currentSum += index;
26 }
27 currentSum -= groupSize * indices[0];
28
29 // Calculate sum of distances for each position in this group
30 for (let i = 0; i < groupSize; i++) {
31 // Calculate the difference from previous index (0 for first element)
32 const indexDiff: number = (i >= 1) ? indices[i] - indices[i - 1] : 0;
33
34 // Update the sum using the formula:
35 // Elements before current position contribute positive difference
36 // Elements after current position contribute negative difference
37 currentSum += i * indexDiff - (groupSize - i) * indexDiff;
38
39 // Store the result at the corresponding position
40 result[indices[i]] = currentSum;
41 }
42 }
43
44 return result;
45}
46
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two main phases:
- Building the dictionary
d
by iterating through the array once:O(n)
- For each unique value in the dictionary:
- Initial sum calculation for the first position:
O(m)
wherem
is the number of occurrences of that value - Iterating through all positions of that value to calculate distances:
O(m)
- Initial sum calculation for the first position:
Since we process each element exactly once in the second phase (as each index appears in exactly one list in the dictionary), the total work done across all unique values is O(n)
. Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The space usage includes:
- Dictionary
d
: stores indices for each unique value, totalingO(n)
space across all lists - Answer array
ans
:O(n)
space - Variables
val
,delta
, and other constants:O(1)
space
The total space complexity is O(n) + O(n) + O(1) = O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Initial Sum Calculation
A common mistake is incorrectly calculating the initial sum for the first element in each group. Developers often try:
# WRONG: This doesn't give the sum of distances from first element
current_sum = sum(indices[1:]) - indices[0] * (count - 1)
Why it's wrong: The formula should account for all indices including the first one, then subtract appropriately.
Correct approach:
# Sum of all indices minus first index times total count
current_sum = sum(indices) - indices[0] * count
2. Off-by-One Error in Delta Update Formula
When updating the sum for subsequent positions, a frequent error is using the wrong counts:
# WRONG: Using (count - i - 1) instead of (count - i) current_sum += i * index_diff - (count - i - 1) * index_diff
Why it's wrong: When at position i
, there are exactly i
elements to the left and count - i
elements at or to the right (including current position).
Correct formula:
current_sum += i * index_diff - (count - i) * index_diff
3. Not Handling Single-Element Groups
The code might break or produce incorrect results if not properly handling groups with only one element:
# Potential issue if not careful with single-element groups
for indices in value_to_indices.values():
if len(indices) == 1:
# Should set result[indices[0]] = 0, not skip
continue # WRONG: This would leave the result as uninitialized
Solution: The current implementation handles this correctly since:
- For single-element groups,
current_sum
becomes 0 naturally - The loop executes once and sets
result[indices[0]] = 0
4. Using Nested Loops for Distance Calculation
A naive approach that recalculates all distances for each element:
# WRONG: O(n²) time complexity in worst case
for i, current_index in enumerate(indices):
total = 0
for j, other_index in enumerate(indices):
if i != j:
total += abs(current_index - other_index)
result[current_index] = total
Why it's inefficient: This has O(k²) complexity for each group of k elements, leading to O(n²) in the worst case when all elements are identical.
Solution: Use the mathematical optimization with running sum as shown in the correct implementation, achieving O(n) overall complexity.
Depth first search is equivalent to which of the tree traversal order?
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!