2121. Intervals Between Identical Elements
Problem Description
The problem provides us with an array arr
where each element may occur more than once. For each element arr[i]
, we want to calculate the sum of distances (intervals) between arr[i]
and all other elements in arr
that have the same value as arr[i]
. In other words, for every element that matches arr[i]
, we sum up the absolute differences in their indices. The output should be an array intervals
of the same length as arr
where each intervals[i]
contains the computed sum for arr[i]
.
To put it simply, if an element appears multiple times in the array, we are looking to find out how far apart all of those occurrences are from each other, summing up these distances for each unique value.
Intuition
The solution approach makes use of a dictionary (or equivalent) to store occurrences of each unique value in the given array. Keys in this dictionary are the unique values, and the values are lists containing the indices where the key occurs.
The overarching idea is to use these lists to calculate the intervals in an efficient manner. A neat observation is that when calculating the sum of intervals for a particular number in arr
, we notice that if the indices are sorted, each index v[i]
will contribute (i * v[i]) - ((m - i) * v[i])
to the total sum, where i
is the current position within the occurrences of the number in arr
, and m
is the total number of occurrences. Put differently, for any given occurrence, the distances from it to all previous occurrences will each increase by v[i] - v[i - 1]
when moving to the next occurrence, and the distances to all next occurrences will each decrease by the same amount.
The algorithm's efficiency comes from leveraging this pattern to update the sum iteratively while traversing through the list of occurrences of each unique value, thereby avoiding the need to recompute the sum from scratch for every element.
Here is a step-by-step breakdown of the solution:
- Create a dictionary (
d
) to group elements ofarr
by their values, mapping each value to a list of indices at which it occurs inarr
. - Initialize an answer array (
ans
) of sizen
with zeros to hold the sum of intervals for each corresponding element inarr
. - Iterate through the grouped values in the dictionary:
- For the current group, calculate the initial interval sum for the first occurrence.
- Iterate through the indices in the current group:
- Calculate the difference between the current index and the previous one.
- Update the running total (
val
), adding the interval increments for indices before the current one and subtracting for indices after. - Store the running total in the corresponding position in the
ans
array.
- Return the
ans
array, which now contains the sum of intervals for each element inarr
.
By organizing the elements in this way and cleverly calculating the contributions of each element's occurrences to the sum, the solution achieves efficient computation of the intervals.
Learn more about Prefix Sum patterns.
Solution Approach
The solution approach can be broken down into the following steps:
-
Group Elements by Value: The
defaultdict(list)
is used to create a dictionaryd
where each key is a value in the arrayarr
, and the corresponding value is a list of indices at which this key appears. Theenumerate
function is used to get both the indexi
and the valuev
as we iterate througharr
. This step essentially groups the occurrences of each element for easy access.d = defaultdict(list) for i, v in enumerate(arr): d[v].append(i)
-
Prepare the Answer Array: An array
ans
of size n (where n is the length of the input arrayarr
) is initialized with zeros. This will be used to store the computed sum of intervals for each element withinarr
.ans = [0] * n
-
Iterate Over the Groups: Then, we iterate through all lists of indices (all values) present in
d
. For each listv
, we calculate the sum of intervals for the first element in the list asval
. Here,m
refers to the total number of occurrences in the listv
.for v in d.values(): m = len(v) val = sum(v) - v[0] * m
-
Compute the Intervals Using a Running Total: As we iterate over the indices in
v
:- We incrementally update
val
by adding the cumulative distance going forward and subtracting the cumulative distance going backwards in the list of occurrences. - The
delta
represents the difference between the current index and the previous one, which affects the cumulative distances in this manner. - The final sum for each occurrence
p
of the value is then saved inans[p]
.
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
- We incrementally update
-
Return the Result: The
ans
array is returned, which holds the sum of intervals for each element of the original arrayarr
.
The solution approach combines dictionary mapping with efficient handling of sequential updates, thereby avoiding redundant calculations. This reduces the complexity considerably compared to a naive approach that might involve nested loops. The use of a running total (incremental adjustment of the val
) to calculate the distances as we iterate through each list of occurrences is key to the efficiency of the solution.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small example to illustrate the solution approach. Suppose we have the following array arr
:
arr = [1, 3, 1, 3, 2, 1]
First, we want to group the elements by value. We will get a dictionary d
that looks like this:
d = { 1: [0, 2, 5], 3: [1, 3], 2: [4] }
Here, the keys of the dictionary are the unique values from arr
, and the values are lists containing the indices at which the corresponding key occurs in arr
.
The answer array ans
will initially be all zeros:
ans = [0, 0, 0, 0, 0, 0] # since there are 6 elements in arr
We iterate over the entries in d
:
-
For the group with key
1
,v
is[0, 2, 5]
. The length ofv
(m
) is 3. The initial interval sumval
is calculated asval = sum([0, 2, 5]) - (0 * 3) = 7
. -
Within the group:
- The difference (
delta
) between the first and second occurrence of1
is2 - 0 = 2
. Updateval
toval + (1 * 2) - (2 * 2) = 7 + 2 - 4 = 5
.ans[2]
is set to5
. - The difference between the second and third occurrence is
5 - 2 = 3
. Updateval
toval + (2 * 3) - (1 * 3) = 5 + 6 - 3 = 8
.ans[5]
is set to8
.
- The difference (
-
Moving on to the group with key
3
,v
is[1, 3]
. Here,m
is 2 and initialval
isval = sum([1, 3]) - (1 * 2) = 4 - 2 = 2
. -
Within the group:
- The difference (
delta
) between the first and second occurrence of3
is3 - 1 = 2
. Updateval
toval + (1 * 2) - (1 * 2) = 2 + 2 - 2 = 2
.ans[1]
andans[3]
are both set to2
since this is the total distance for both occurrences.
- The difference (
-
Finally, for the group with key
2
,v
is[4]
. Because there is only one occurrence, the distance is zero.ans[4]
remains0
.
The final ans
array will be:
ans = [0, 2, 5, 2, 0, 8]
In this final array, each ans[i]
is the sum of intervals between the occurrences of arr[i]
in the original array. This efficiently solves the problem by using a dictionary to map values to indices and by calculating the sum incrementally.
The solution leverages the spatial ordering of index occurrences, making use of a running total instead of performing redundant calculations, which would happen if doing this for each element individually without considering the previously calculated distances.
Solution Implementation
1from collections import defaultdict
2
3class Solution:
4 def getDistances(self, arr: List[int]) -> List[int]:
5 # Dictionary to store the indices of each unique value in the array
6 index_map = defaultdict(list)
7 # Length of the array
8 n = len(arr)
9
10 # Populate the dictionary with the indices for each value
11 for i, value in enumerate(arr):
12 index_map[value].append(i)
13
14 # Initialize the answer list with zeros
15 answer = [0] * n
16
17 # Loop through the dictionary to calculate the distances for each value
18 for indices in index_map.values():
19 # Calculate the initial total distance for the first occurrence
20 m = len(indices)
21 total_distance = sum(indices) - indices[0] * m
22
23 # Calculate the distances for all other occurrences of the same value
24 for i, index in enumerate(indices):
25 # Calculate the change in distance when moving from one occurrence to the next
26 delta = indices[i] - indices[i - 1] if i >= 1 else 0
27 # Update the total distance considering the number of elements to the left and right
28 total_distance += i * delta - (m - i) * delta
29 # Store the total distance at the current index in the answer list
30 answer[index] = total_distance
31
32 return answer
33
1class Solution {
2 public long[] getDistances(int[] arr) {
3 // A map to store the indices of each unique value in the array
4 Map<Integer, List<Integer>> indexMap = new HashMap<>();
5 int length = arr.length;
6
7 // Populate the map with indices for each value
8 for (int i = 0; i < length; ++i) {
9 indexMap.computeIfAbsent(arr[i], k -> new ArrayList<>()).add(i);
10 }
11
12 // Array to store the result
13 long[] result = new long[length];
14
15 // Iterate over each list of indices for all unique values
16 for (List<Integer> indices : indexMap.values()) {
17 int size = indices.size();
18 long sumIndices = 0;
19
20 // Calculate the sum of all indices for the current value
21 for (int index : indices) {
22 sumIndices += index;
23 }
24
25 // Initialize the sum for the first location of this value
26 sumIndices -= (size * indices.get(0));
27
28 // Calculate the total distance for each index occurrence
29 for (int i = 0; i < size; ++i) {
30 // Difference between the current and previous index
31 int delta = i >= 1 ? indices.get(i) - indices.get(i - 1) : 0;
32 // Update sumIndices to include distances of current index from others
33 sumIndices += i * delta - (size - i) * delta;
34 // Set the total distance for the current index in the result array
35 result[indices.get(i)] = sumIndices;
36 }
37 }
38
39 return result;
40 }
41}
42
1class Solution {
2public:
3 vector<long long> getDistances(vector<int>& arr) {
4 unordered_map<int, vector<int>> indexMap; // Maps each unique value to the indices it appears at
5
6 int size = arr.size(); // Size of the input array
7 // Index each element in the map
8 for (int i = 0; i < size; ++i) {
9 indexMap[arr[i]].push_back(i);
10 }
11
12 vector<long long> answer(size); // Initialize the answer vector with the size of the input array
13 // Iterate over each element in the map to compute the sum of distances
14 for (auto& item : indexMap) {
15 auto& indices = item.second; // References the vector of indices for this element value
16 int count = indices.size(); // Number of occurrences of this element value
17 long long sum = 0;
18
19 // Calculate the initial sum of distances from the first occurrence
20 for (int index : indices) {
21 sum += index;
22 }
23 sum -= count * indices[0]; // Subtract distances as though all elements were at the first occurrence
24
25 // Loop over indices to distribute the sum to the appropriate position in the answer vector
26 for (int i = 0; i < indices.size(); ++i) {
27 // Calculate the difference between the positions of the current and previous occurrences
28 int delta = i >= 1 ? indices[i] - indices[i - 1] : 0;
29 // Update the sum: add the difference for the previous occurrences,
30 // and subtract it for the remaining occurrences
31 sum += i * delta - (count - i) * delta;
32 // Assign the sum to the index of the current occurrence in the answer vector
33 answer[indices[i]] = sum;
34 }
35 }
36 return answer; // Return the computed sum of distances vector
37 }
38};
39
1// Define the type for mapping each unique value to the indices it appears at
2let indexMap: Record<number, number[]> = {};
3
4// Define the method signature with the appropriate TypeScript types
5function getDistances(arr: number[]): bigint[] {
6 indexMap = {}; // Reset the indexMap for each call
7 const size: number = arr.length; // Size of the input array
8
9 // Index each element in the map
10 for (let i = 0; i < size; ++i) {
11 if (!indexMap[arr[i]]) {
12 indexMap[arr[i]] = [];
13 }
14 indexMap[arr[i]].push_back(i);
15 }
16
17 // Initialize the answer array with the size of the input array,
18 // filled with BigInts representing zero for each element
19 let answer: bigint[] = new Array<bigint>(size).fill(BigInt(0));
20
21 // Iterate over each element in the map to compute the sum of distances
22 for (let itemKey in indexMap) {
23 let indices: number[] = indexMap[itemKey]; // References the array of indices for this element value
24 let count: number = indices.length; // Number of occurrences of this element value
25 let sum: bigint = BigInt(0);
26
27 // Calculate the initial sum of distances from the first occurrence
28 for (let index of indices) {
29 sum += BigInt(index);
30 }
31 sum -= BigInt(count * indices[0]); // Subtract distances as though all elements were at the first occurrence
32
33 // Loop over indices to distribute the sum to the appropriate position in the answer array
34 for (let i = 0; i < indices.length; ++i) {
35 // Calculate the difference between the positions of the current and previous occurrences
36 let delta: number = i >= 1 ? indices[i] - indices[i - 1] : 0;
37 // Update the sum: add the difference for the previous occurrences,
38 // and subtract it for the remaining occurrences
39 sum += BigInt(i * delta - (count - i) * delta);
40 // Assign the sum to the index of the current occurrence in the answer array
41 answer[indices[i]] = sum;
42 }
43 }
44 return answer; // Return the computed sum of distances array
45}
46
Time and Space Complexity
The given Python code involves creating a dictionary to group indices of identical elements and then computing the sum of distances for each unique element in the array.
Time Complexity:
-
Constructing the dictionary
d
where each value is a list of indices has a time complexity ofO(n)
, wheren
is the length of the arrayarr
. This is because we iterate over the array once. -
The outer loop
for v in d.values():
has at mostn
iterations in the worst-case scenario where all array elements are unique. -
Inside this loop, we calculate the initial
val
for each group of identical elements. The summationsum(v)
isO(m)
, wherem
is the number of times a distinct element appears in the array. The termv[0] * m
isO(m)
since it involvesm
multiplications. As such, this part has a time complexity ofO(m)
for each unique element. -
The inner loop
for i, p in enumerate(v):
iteratesm
times (wherem
is the size of the listv
). The calculation within this loop isO(1)
, as it consists of simple arithmetic operations. Therefore, the time complexity for the inner loop isO(m)
.
Combining these observations, the overall time complexity of the algorithm depends on the distribution of elements in the array. In the worst-case scenario, where all elements are unique, the time complexity tends to O(n * m)
which simplifies to O(n)
. In the average and expected case, where elements are repeated, the time complexity would be O(n + k * m)
where k
is the number of unique elements. However, since the sum of all m
for each unique element is n
, the average-case time complexity remains linear, or O(n)
.
Space Complexity:
-
The space complexity of the dictionary
d
can be up toO(n)
in the case where all elements are distinct since we store an index for each element. -
The
ans
list also takes upO(n)
space since it stores the result for each element in thearr
. -
Auxiliary space complexity is
O(1)
as there are no other data structures that grow with input size; only a fixed number of variables are used.
Therefore, the space complexity of the code is O(n)
accounting for the space required for the dictionary d
and the answer array ans
.
Learn more about how to find time and space complexity quickly using problem constraints.
How does merge sort divide the problem into subproblems?
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!