Facebook Pixel

3740. Minimum Distance Between Three Equal Elements I

EasyArrayHash Table
LeetCode ↗

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Hash Table / Counting?

This problem maps to Hash Table / Counting through a short path in the full flowchart.

Tree orgraph?noFastlookup orcounting?yesHash Table /Counting

Grouping indices by value and sliding a window of three consecutive occurrences finds the minimum gap with hash-table-based index storage.

Open in Flowchart

Intuition

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 - i
  • abs(j - k) = k - j
  • abs(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), where n is the length of the array nums. Building the hash table takes O(n) time, and the total work across all index lists in the sliding-window scan is also O(n), since the combined length of all lists equals n.
  • 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 iValue xActionHash Table State
02append 0 to g[2]{2: [0]}
15append 1 to g[5]{2: [0], 5: [1]}
22append 2 to g[2]{2: [0, 2], 5: [1]}
38append 3 to g[8]{2: [0, 2], 5: [1], 8: [3]}
45append 4 to g[5]{2: [0, 2], 5: [1, 4], 8: [3]}
52append 5 to g[2]{2: [0, 2, 5], 5: [1, 4], 8: [3]}
65append 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
28
1class 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}
38
1class 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};
32
1/**
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}
50

Time and Space Complexity

Time Complexity: O(n)

The analysis is as follows:

  1. Building the hash map: The first loop iterates over all elements of nums using enumerate, appending each index to the corresponding list in the dictionary g. This takes O(n) time, where n is the length of the array.

  2. Iterating over the grouped indices: The second part iterates over g.values(), and for each list ls, the inner loop runs over its indices. Although there are nested loops, the total number of index entries across all lists in g equals n (each element of nums contributes exactly one index). Therefore, the combined work of the outer and inner loops is O(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 < knot 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 Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More