3740. Minimum Distance Between Three Equal Elements I
Problem Description
You are given an integer array nums.
A tuple (i, j, k) of 3 distinct indices is called good if the values at those positions are all equal, meaning nums[i] == nums[j] == nums[k].
For any good tuple, its distance is defined as abs(i - j) + abs(j - k) + abs(k - i), where abs(x) represents the absolute value of x.
Your task is to find and return the minimum possible distance among all good tuples. If no good tuple exists in the array, return -1.
To understand the distance formula better, suppose we sort the three indices so that i < j < k. Then the distance simplifies to (j - i) + (k - j) + (k - i) = 2 * (k - i). This tells us the distance depends only on the gap between the smallest and largest index in the tuple. Therefore, to minimize the distance for a given value, we want to pick three indices (sharing the same value) that are as close together as possible.
For example, if a value appears at sorted positions [1, 3, 5, 8], the closest grouping of three consecutive positions would be [1, 3, 5] or [3, 5, 8], and we compute 2 * (k - i) for each window of three consecutive indices to find the smallest result.
How We Pick the Algorithm
Why Hash Table / Counting?
This problem maps to Hash Table / Counting through a short path in the full flowchart.
Grouping indices by value and sliding a window of three consecutive occurrences finds the minimum gap with hash-table-based index storage.
Open in FlowchartIntuition
The key observation comes from simplifying the distance formula. A good tuple requires all three values to be equal, so naturally we should group indices by their values first. Only values that appear at least three times can possibly form a good tuple.
Next, let's simplify the distance expression. For any three distinct indices, suppose we arrange them in increasing order as i < j < k. Then:
abs(i - j) = j - iabs(j - k) = k - jabs(k - i) = k - i
Adding these together gives (j - i) + (k - j) + (k - i) = 2 * (k - i). The middle index j cancels out completely! This means the distance depends only on the smallest index i and the largest index k, and not at all on the middle one.
Since the distance is just 2 * (k - i), minimizing it means making the gap between the largest and smallest chosen index as small as possible. For a fixed value, we already have its indices stored in sorted order (because we collected them while traversing the array left to right). To make k - i as small as possible while still picking three indices, we should choose three consecutive indices from this sorted list.
Why consecutive? If we picked the smallest index i and the largest index k from a list, the gap k - i is fixed by that pair. To keep this gap minimal, both endpoints should be as close as possible, which happens when we slide a window of size three across the sorted list. For each window, the first and third elements give us the smallest possible k - i for that particular triple.
So the strategy becomes clear: for every value, scan through its sorted index list using a sliding window of three consecutive indices, compute 2 * (ls[h + 2] - ls[h]) for each window, and track the overall minimum. If no value has three or more indices, no good tuple exists, and we return -1.
Solution Approach
Solution 1: Hash Table
We use a hash table g to store the list of indices for each number in the array. While traversing the array, we add each number's index to its corresponding list in the hash table. Define a variable ans to store the answer, with an initial value of infinity inf.
Step 1: Build the hash table.
We iterate through nums using enumerate to access both the index i and the value x. For each value, we append its index to the list g[x]. Because we traverse from left to right, each value's index list is automatically kept in sorted (increasing) order, which is essential for the next step.
g = defaultdict(list)
for i, x in enumerate(nums):
g[x].append(i)
Step 2: Scan each index list with a sliding window.
Next, we iterate through each index list in the hash table. If the length of an index list for a particular number is greater than or equal to 3, it means there exists a valid triplet. To minimize the distance, we choose three consecutive indices from that number's index list.
For a sorted list ls, we slide a window of size three by letting h range from 0 to len(ls) - 3. Within each window, the smallest index is i = ls[h] and the largest is k = ls[h + 2]. The middle index does not matter because the distance simplifies to 2 * (k - i). We compute this distance and update ans with the minimum.
for ls in g.values():
for h in range(len(ls) - 2):
i, k = ls[h], ls[h + 2]
ans = min(ans, (k - i) * 2)
Note that range(len(ls) - 2) naturally handles lists with fewer than three elements: when len(ls) < 3, the range is empty and the inner loop simply does not execute.
Step 3: Return the result.
Finally, if the answer is still the initial value inf, it means no valid triplet exists, so we return -1; otherwise, we return the calculated minimum distance.
return -1 if ans == inf else ans
Complexity Analysis:
- Time complexity:
O(n), wherenis the length of the arraynums. Building the hash table takesO(n)time, and the total work across all index lists in the sliding-window scan is alsoO(n), since the combined length of all lists equalsn. - Space complexity:
O(n), due to the hash table storing every index exactly once.
Example Walkthrough
Let's trace through the solution approach using a small example.
Input: nums = [2, 5, 2, 8, 5, 2, 5]
Step 1: Build the hash table.
We traverse the array from left to right, recording the index of each value into the hash table g. Because we scan left to right, each list stays in increasing (sorted) order automatically.
Index i | Value x | Action | Hash Table State |
|---|---|---|---|
| 0 | 2 | append 0 to g[2] | {2: [0]} |
| 1 | 5 | append 1 to g[5] | {2: [0], 5: [1]} |
| 2 | 2 | append 2 to g[2] | {2: [0, 2], 5: [1]} |
| 3 | 8 | append 3 to g[8] | {2: [0, 2], 5: [1], 8: [3]} |
| 4 | 5 | append 4 to g[5] | {2: [0, 2], 5: [1, 4], 8: [3]} |
| 5 | 2 | append 5 to g[2] | {2: [0, 2, 5], 5: [1, 4], 8: [3]} |
| 6 | 5 | append 6 to g[5] | {2: [0, 2, 5], 5: [1, 4, 6], 8: [3]} |
Final hash table:
g[2] = [0, 2, 5]g[5] = [1, 4, 6]g[8] = [3]
We initialize ans = inf.
Step 2: Scan each index list with a sliding window.
We only care about lists with three or more indices, since a good tuple needs three positions sharing the same value.
Value 2 → list [0, 2, 5] (length 3, so one window):
- Window
h = 0:i = ls[0] = 0,k = ls[2] = 5. - Distance =
2 * (k - i) = 2 * (5 - 0) = 10. - Update
ans = min(inf, 10) = 10.
Value 5 → list [1, 4, 6] (length 3, so one window):
- Window
h = 0:i = ls[0] = 1,k = ls[2] = 6. - Distance =
2 * (k - i) = 2 * (6 - 1) = 10. - Update
ans = min(10, 10) = 10.
Value 8 → list [3] (length 1):
range(len(ls) - 2) = range(-1)is empty, so the inner loop is skipped entirely. No triplet is possible here.
Step 3: Return the result.
Since ans = 10 is no longer inf, we return 10.
Verification: Consider the good tuple of indices (0, 2, 5) where all values equal 2. Using the original formula:
abs(0 - 2) + abs(2 - 5) + abs(5 - 0) = 2 + 3 + 5 = 10. ✅
This matches the simplified computation 2 * (5 - 0) = 10, confirming the middle index cancels out and the distance depends only on the smallest and largest indices.
Solution Implementation
1from collections import defaultdict
2from math import inf
3from typing import List
4
5
6class Solution:
7 def minimumDistance(self, nums: List[int]) -> int:
8 # Map each distinct value to the sorted list of indices where it appears.
9 # Since we iterate nums in order, the indices are naturally ascending.
10 value_to_indices: dict[int, List[int]] = defaultdict(list)
11 for index, value in enumerate(nums):
12 value_to_indices[value].append(index)
13
14 # Track the smallest distance found across all valid triples.
15 min_distance = inf
16
17 # For each group of equal values, examine every set of three
18 # consecutive occurrences (positions start, start+1, start+2).
19 for indices in value_to_indices.values():
20 for start in range(len(indices) - 2):
21 first_index = indices[start] # outer-left occurrence
22 third_index = indices[start + 2] # outer-right occurrence
23 # Distance is the span between the outer indices, doubled.
24 min_distance = min(min_distance, (third_index - first_index) * 2)
25
26 # If no valid triple existed, min_distance stays as infinity.
27 return -1 if min_distance == inf else min_distance
281class Solution {
2 public int minimumDistance(int[] nums) {
3 int n = nums.length;
4
5 // Group the indices of equal values together.
6 // Key: the value found in nums; Value: list of indices that hold this value.
7 Map<Integer, List<Integer>> valueToIndices = new HashMap<>();
8 for (int i = 0; i < n; ++i) {
9 valueToIndices
10 .computeIfAbsent(nums[i], key -> new ArrayList<>())
11 .add(i);
12 }
13
14 // Use a large sentinel to represent "no answer found yet".
15 final int inf = 1 << 30;
16 int ans = inf;
17
18 // Examine every group of equal values.
19 for (List<Integer> indices : valueToIndices.values()) {
20 int size = indices.size();
21
22 // For each window of three consecutive indices (head, head+1, head+2),
23 // pair the first and the third index.
24 for (int head = 0; head < size - 2; ++head) {
25 int firstIndex = indices.get(head);
26 int thirdIndex = indices.get(head + 2);
27
28 // The distance contribution is twice the index gap between them.
29 int distance = (thirdIndex - firstIndex) * 2;
30 ans = Math.min(ans, distance);
31 }
32 }
33
34 // If no valid window was found, return -1; otherwise return the minimum distance.
35 return ans == inf ? -1 : ans;
36 }
37}
381class Solution {
2public:
3 int minimumDistance(vector<int>& nums) {
4 int n = nums.size();
5
6 // Group indices by their value: value -> list of positions where it appears
7 unordered_map<int, vector<int>> valueToIndices;
8 for (int i = 0; i < n; ++i) {
9 valueToIndices[nums[i]].push_back(i);
10 }
11
12 const int inf = 1 << 30;
13 int ans = inf;
14
15 // For each group of equal values, examine triples of consecutive
16 // occurrences (h, h+1, h+2). The cost is twice the gap between the
17 // first and the third index of the triple.
18 for (auto& [value, indices] : valueToIndices) {
19 int m = indices.size();
20 for (int h = 0; h < m - 2; ++h) {
21 int first = indices[h]; // index of the first occurrence in the triple
22 int third = indices[h + 2]; // index of the third occurrence in the triple
23 int cost = (third - first) * 2;
24 ans = min(ans, cost);
25 }
26 }
27
28 // If no valid triple was found, return -1; otherwise return the minimum cost.
29 return ans == inf ? -1 : ans;
30 }
31};
321/**
2 * Computes the minimum distance metric based on indices of equal values.
3 *
4 * Approach:
5 * 1. Group the positions (indices) of each distinct value in `nums`.
6 * 2. Within every group, examine pairs of indices that are two apart
7 * in the grouped list (i.e. group[h] and group[h + 2]).
8 * 3. The candidate value is twice the gap between those two indices.
9 * 4. Track and return the smallest candidate; return -1 if none exists.
10 *
11 * @param nums - The input array of numbers.
12 * @returns The minimum computed distance, or -1 if no valid triple exists.
13 */
14function minimumDistance(nums: number[]): number {
15 const length = nums.length;
16
17 // Map each distinct value to the sorted list of indices where it occurs.
18 const valueToIndices = new Map<number, number[]>();
19
20 for (let index = 0; index < length; index++) {
21 const value = nums[index];
22 if (!valueToIndices.has(value)) {
23 valueToIndices.set(value, []);
24 }
25 valueToIndices.get(value)!.push(index);
26 }
27
28 // A large sentinel representing "no answer found yet".
29 const infinity = 1 << 30;
30 let answer = infinity;
31
32 // Examine each group of equal-valued positions.
33 for (const indices of valueToIndices.values()) {
34 const groupSize = indices.length;
35
36 // Slide over indices that are two positions apart in the group.
37 for (let offset = 0; offset < groupSize - 2; offset++) {
38 const startIndex = indices[offset];
39 const endIndex = indices[offset + 2];
40
41 // Candidate distance is twice the index gap.
42 const candidate = (endIndex - startIndex) * 2;
43 answer = Math.min(answer, candidate);
44 }
45 }
46
47 // If no valid candidate was found, return -1.
48 return answer === infinity ? -1 : answer;
49}
50Time and Space Complexity
Time Complexity: O(n)
The analysis is as follows:
-
Building the hash map: The first loop iterates over all elements of
numsusingenumerate, appending each index to the corresponding list in the dictionaryg. This takesO(n)time, wherenis the length of the array. -
Iterating over the grouped indices: The second part iterates over
g.values(), and for each listls, the inner loop runs over its indices. Although there are nested loops, the total number of index entries across all lists ingequalsn(each element ofnumscontributes exactly one index). Therefore, the combined work of the outer and inner loops isO(n).
Summing these up, the overall time complexity is O(n).
Space Complexity: O(n)
The dictionary g stores every index of nums distributed across its lists. In total, it holds n index values, so the space required is O(n). Other variables such as ans, i, k, and h use only constant extra space. Hence, the space complexity is O(n), where n is the length of the array.
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Forgetting the factor of 2 in the distance formula
A very common mistake is returning third_index - first_index instead of (third_index - first_index) * 2. The distance is defined as abs(i - j) + abs(j - k) + abs(k - i), which simplifies to 2 * (k - i) for sorted indices i < j < k — not just k - i.
# WRONG: forgets to double the span
min_distance = min(min_distance, third_index - first_index)
# CORRECT: distance is the doubled span
min_distance = min(min_distance, (third_index - first_index) * 2)
Why it happens: The middle term cancels out in the simplification, so it's tempting to think the answer is just the gap between outer indices. Always trace through the algebra: (j - i) + (k - j) + (k - i) = 2 * (k - i).
Pitfall 2: Assuming non-consecutive triples could be optimal
Some attempt to compare all combinations of three indices (an O(m³) approach per value group), believing that skipping indices might produce a smaller distance. This is both unnecessary and inefficient.
Because the distance only depends on the span between the smallest and largest chosen index (2 * (k - i)), the optimal triple for any value is always three consecutive occurrences in its sorted index list. Picking non-adjacent indices can only widen the span.
# WRONG: O(m^3) brute force over all triples in a group — too slow
from itertools import combinations
for i, j, k in combinations(indices, 3):
min_distance = min(min_distance, (max(i, j, k) - min(i, j, k)) * 2)
# CORRECT: O(m) sliding window over consecutive triples only
for start in range(len(indices) - 2):
min_distance = min(min_distance, (indices[start + 2] - indices[start]) * 2)
Why it's safe: For a fixed window of three consecutive entries indices[start..start+2], the span is minimized; expanding the window or skipping an index between them never decreases k - i.
Pitfall 3: Off-by-one errors in the sliding window range
Iterating with the wrong bound causes either an IndexError or a missed final window.
# WRONG: IndexError when start + 2 goes out of bounds
for start in range(len(indices)):
... indices[start + 2] ... # crashes near the end
# WRONG: misses the last valid window (stops one too early)
for start in range(len(indices) - 3):
...
# CORRECT: range(len(indices) - 2) covers exactly the valid windows
for start in range(len(indices) - 2):
first_index, third_index = indices[start], indices[start + 2]
Why len(indices) - 2 is correct: The last valid start index must satisfy start + 2 <= len(indices) - 1, i.e. start <= len(indices) - 3, so range(len(indices) - 2) produces exactly the values 0 through len(indices) - 3. As a bonus, when a group has fewer than three elements, the range is empty and the loop is safely skipped — no explicit if len(indices) >= 3 check is needed.
Pitfall 4: Returning inf (or 0) instead of -1 when no triple exists
If no value appears at least three times, min_distance stays at its sentinel inf. Forgetting the final guard returns a nonsensical value.
# WRONG: leaks the sentinel value return min_distance # WRONG: initializing to 0 hides the "no triple" case and returns 0 incorrectly min_distance = 0 ... return min_distance # CORRECT: convert the untouched sentinel back to -1 return -1 if min_distance == inf else min_distance
Why initialize with inf: Using inf as the starting value lets min(...) work cleanly on the first real comparison, while remaining distinguishable from any genuine computed distance (which is always a finite, non-negative integer).
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich data structure is used to implement recursion?
Recommended Readings
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity describes how the time needed
Want a Structured Path to Master System Design Too? Don’t Miss This!