Facebook Pixel

1182. Shortest Distance to Target Color 🔒

Problem Description

You have an array colors containing only three possible values: 1, 2, and 3, representing three different colors.

You need to answer multiple queries about this array. Each query gives you:

  • An index i (position in the array)
  • A target color c (which is 1, 2, or 3)

For each query, find the shortest distance from index i to any occurrence of color c in the array. The distance is calculated as the absolute difference between indices.

If color c doesn't exist anywhere in the array, or cannot be reached from index i, return -1 for that query.

Example:

  • If colors = [1, 1, 2, 1, 3, 2, 2, 3, 3] and query is (i=1, c=3)
  • You need to find the closest 3 to index 1
  • The 3s are at indices 4, 7, and 8
  • The distances are |1-4|=3, |1-7|=6, and |1-8|=7
  • The shortest distance is 3

The function should return a list of answers corresponding to each query in the order they were given.

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

Intuition

When we need to find the shortest distance from a given index to a target color, we essentially need to look in two directions: left and right. The closest occurrence of the target color could be on either side.

The naive approach would be to search both directions for each query, but this would be inefficient if we have many queries. Instead, we can preprocess the array once to store useful information that helps us answer all queries quickly.

The key insight is that for any position i, we only need to know:

  • Where is the nearest occurrence of each color to the left of i?
  • Where is the nearest occurrence of each color to the right of i?

With this information preprocessed, answering each query becomes a simple calculation: for query (i, c), the answer is the minimum of:

  • Distance to the nearest color c on the left: i - left_position
  • Distance to the nearest color c on the right: right_position - i

To build these preprocessing arrays:

  • For the right array: We scan from right to left. At each position, we either encounter the color (update its position) or carry forward the previously known nearest position of each color.
  • For the left array: We scan from left to right. Similar logic applies - we track the most recent occurrence of each color as we move forward.

We use infinity and -infinity as initial values to handle edge cases where a color doesn't exist on one side. If the calculated minimum distance exceeds the array length, it means the color doesn't exist in the array at all, so we return -1.

This preprocessing approach transforms a potentially O(n) operation per query into an O(1) lookup, making the solution efficient for multiple queries.

Learn more about Binary Search and Dynamic Programming patterns.

Solution Approach

The solution uses preprocessing to build two auxiliary arrays that store the nearest positions of each color from both directions.

Data Structures:

  • right: A 2D array of size (n+1) × 3 where right[i][j] stores the index of the nearest occurrence of color j+1 at or to the right of position i
  • left: A 2D array of size (n+1) × 3 where left[i][j] stores the index of the nearest occurrence of color j+1 at or to the left of position i-1

Implementation Steps:

  1. Initialize the preprocessing arrays:

    • Set right[n][0] = right[n][1] = right[n][2] = inf (no colors exist beyond the array)
    • Set left[0][0] = left[0][1] = left[0][2] = -inf (no colors exist before the array)
  2. Build the right array (scanning right to left):

    for i in range(n - 1, -1, -1):
        for j in range(3):
            right[i][j] = right[i + 1][j]  # Copy values from the right
        right[i][colors[i] - 1] = i        # Update current color's position
    • For each position i, first copy all nearest positions from i+1
    • Then update the position for the current color at index i
  3. Build the left array (scanning left to right):

    for i, c in enumerate(colors, 1):
        for j in range(3):
            left[i][j] = left[i - 1][j]   # Copy values from the left
        left[i][c - 1] = i - 1             # Update current color's position
    • Note: left array is 1-indexed for easier query processing
    • Similar logic: copy previous values, then update current color
  4. Process queries:

    for i, c in queries:
        d = min(i - left[i + 1][c - 1], right[i][c - 1] - i)
        ans.append(-1 if d > n else d)
    • For query (i, c), calculate:
      • Distance to nearest c on the left: i - left[i + 1][c - 1]
      • Distance to nearest c on the right: right[i][c - 1] - i
    • Take the minimum of these two distances
    • If the distance exceeds n, the color doesn't exist, return -1

Time Complexity: O(n + q) where n is the array length and q is the number of queries Space Complexity: O(n) for the preprocessing arrays

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with colors = [1, 2, 1, 3] and queries [(0, 3), (2, 2), (1, 1)].

Step 1: Initialize preprocessing arrays

  • Array length n = 4
  • right array (size 5×3): Initialize right[4] with [inf, inf, inf]
  • left array (size 5×3): Initialize left[0] with [-inf, -inf, -inf]

Step 2: Build right array (scan right to left)

Starting from index 3 (rightmost):

  • i = 3: colors[3] = 3

    • Copy from right[4]: right[3] = [inf, inf, inf]
    • Update color 3: right[3][2] = 3
    • Result: right[3] = [inf, inf, 3]
  • i = 2: colors[2] = 1

    • Copy from right[3]: right[2] = [inf, inf, 3]
    • Update color 1: right[2][0] = 2
    • Result: right[2] = [2, inf, 3]
  • i = 1: colors[1] = 2

    • Copy from right[2]: right[1] = [2, inf, 3]
    • Update color 2: right[1][1] = 1
    • Result: right[1] = [2, 1, 3]
  • i = 0: colors[0] = 1

    • Copy from right[1]: right[0] = [2, 1, 3]
    • Update color 1: right[0][0] = 0
    • Result: right[0] = [0, 1, 3]

Step 3: Build left array (scan left to right)

  • i = 1: colors[0] = 1

    • Copy from left[0]: left[1] = [-inf, -inf, -inf]
    • Update color 1: left[1][0] = 0
    • Result: left[1] = [0, -inf, -inf]
  • i = 2: colors[1] = 2

    • Copy from left[1]: left[2] = [0, -inf, -inf]
    • Update color 2: left[2][1] = 1
    • Result: left[2] = [0, 1, -inf]
  • i = 3: colors[2] = 1

    • Copy from left[2]: left[3] = [0, 1, -inf]
    • Update color 1: left[3][0] = 2
    • Result: left[3] = [2, 1, -inf]
  • i = 4: colors[3] = 3

    • Copy from left[3]: left[4] = [2, 1, -inf]
    • Update color 3: left[4][2] = 3
    • Result: left[4] = [2, 1, 3]

Step 4: Process queries

Query 1: (i=0, c=3)

  • Distance to left: 0 - left[1][2] = 0 - (-inf) = inf
  • Distance to right: right[0][2] - 0 = 3 - 0 = 3
  • Minimum: min(inf, 3) = 3
  • Answer: 3

Query 2: (i=2, c=2)

  • Distance to left: 2 - left[3][1] = 2 - 1 = 1
  • Distance to right: right[2][1] - 2 = inf - 2 = inf
  • Minimum: min(1, inf) = 1
  • Answer: 1

Query 3: (i=1, c=1)

  • Distance to left: 1 - left[2][0] = 1 - 0 = 1
  • Distance to right: right[1][0] - 1 = 2 - 1 = 1
  • Minimum: min(1, 1) = 1
  • Answer: 1

Final Result: [3, 1, 1]

Solution Implementation

1class Solution:
2    def shortestDistanceColor(
3        self, colors: List[int], queries: List[List[int]]
4    ) -> List[int]:
5        """
6        Find the shortest distance from each query position to the nearest element 
7        with the specified color.
8      
9        Args:
10            colors: List of colors (1, 2, or 3) at each position
11            queries: List of [position, target_color] pairs
12      
13        Returns:
14            List of shortest distances for each query (-1 if color not found)
15        """
16        n = len(colors)
17      
18        # Build right array: nearest position of each color to the right (including current)
19        # right[i][j] stores the nearest position >= i where color j+1 appears
20        nearest_right = [[float('inf')] * 3 for _ in range(n + 1)]
21      
22        # Traverse from right to left to find nearest color positions to the right
23        for position in range(n - 1, -1, -1):
24            # Copy values from the next position
25            for color_idx in range(3):
26                nearest_right[position][color_idx] = nearest_right[position + 1][color_idx]
27            # Update current color's position
28            current_color = colors[position] - 1  # Convert to 0-indexed
29            nearest_right[position][current_color] = position
30      
31        # Build left array: nearest position of each color to the left (including current)
32        # left[i][j] stores the nearest position <= i-1 where color j+1 appears
33        nearest_left = [[float('-inf')] * 3 for _ in range(n + 1)]
34      
35        # Traverse from left to right to find nearest color positions to the left
36        for position, color in enumerate(colors, 1):
37            # Copy values from the previous position
38            for color_idx in range(3):
39                nearest_left[position][color_idx] = nearest_left[position - 1][color_idx]
40            # Update current color's position
41            current_color = color - 1  # Convert to 0-indexed
42            nearest_left[position][current_color] = position - 1
43      
44        # Process each query
45        result = []
46        for query_position, target_color in queries:
47            # Calculate distance to nearest target color on the left and right
48            target_color_idx = target_color - 1  # Convert to 0-indexed
49          
50            # Distance to the nearest target color on the left
51            distance_to_left = query_position - nearest_left[query_position + 1][target_color_idx]
52          
53            # Distance to the nearest target color on the right
54            distance_to_right = nearest_right[query_position][target_color_idx] - query_position
55          
56            # Get minimum distance
57            min_distance = min(distance_to_left, distance_to_right)
58          
59            # If distance exceeds array length, color doesn't exist
60            if min_distance > n:
61                result.append(-1)
62            else:
63                result.append(min_distance)
64      
65        return result
66
1class Solution {
2    public List<Integer> shortestDistanceColor(int[] colors, int[][] queries) {
3        int n = colors.length;
4        final int INF = 1 << 30;  // Large value representing infinity
5      
6        // rightNearest[i][c] stores the index of nearest element with color c+1 to the right of position i (inclusive)
7        int[][] rightNearest = new int[n + 1][3];
8        Arrays.fill(rightNearest[n], INF);  // Initialize last row with infinity (no elements to the right)
9      
10        // Build rightNearest array by scanning from right to left
11        for (int i = n - 1; i >= 0; i--) {
12            // Copy values from next position
13            for (int color = 0; color < 3; color++) {
14                rightNearest[i][color] = rightNearest[i + 1][color];
15            }
16            // Update current position's color
17            rightNearest[i][colors[i] - 1] = i;
18        }
19      
20        // leftNearest[i][c] stores the index of nearest element with color c+1 to the left of position i-1 (inclusive)
21        int[][] leftNearest = new int[n + 1][3];
22        Arrays.fill(leftNearest[0], -INF);  // Initialize first row with negative infinity (no elements to the left)
23      
24        // Build leftNearest array by scanning from left to right
25        for (int i = 1; i <= n; i++) {
26            // Copy values from previous position
27            for (int color = 0; color < 3; color++) {
28                leftNearest[i][color] = leftNearest[i - 1][color];
29            }
30            // Update current position's color
31            leftNearest[i][colors[i - 1] - 1] = i - 1;
32        }
33      
34        // Process each query
35        List<Integer> result = new ArrayList<>();
36        for (int[] query : queries) {
37            int targetIndex = query[0];
38            int targetColor = query[1] - 1;  // Convert to 0-indexed
39          
40            // Calculate distance to nearest element with target color
41            // leftNearest[targetIndex + 1][targetColor] gives nearest to the left (including current position)
42            // rightNearest[targetIndex][targetColor] gives nearest to the right (including current position)
43            int leftDistance = targetIndex - leftNearest[targetIndex + 1][targetColor];
44            int rightDistance = rightNearest[targetIndex][targetColor] - targetIndex;
45            int minDistance = Math.min(leftDistance, rightDistance);
46          
47            // If minimum distance is greater than array length, no such color exists
48            result.add(minDistance > n ? -1 : minDistance);
49        }
50      
51        return result;
52    }
53}
54
1class Solution {
2public:
3    vector<int> shortestDistanceColor(vector<int>& colors, vector<vector<int>>& queries) {
4        int n = colors.size();
5      
6        // rightmost[i][j] stores the nearest index >= i where color j+1 appears
7        // rightmost[n][j] is initialized to INF (no color found to the right)
8        int rightmost[n + 1][3];
9        const int INF = 1 << 30;
10        fill(rightmost[n], rightmost[n] + 3, INF);
11      
12        // Build rightmost array by traversing from right to left
13        for (int i = n - 1; i >= 0; --i) {
14            // Copy values from the next position
15            for (int j = 0; j < 3; ++j) {
16                rightmost[i][j] = rightmost[i + 1][j];
17            }
18            // Update the position for current color
19            rightmost[i][colors[i] - 1] = i;
20        }
21      
22        // leftmost[i][j] stores the nearest index < i where color j+1 appears
23        // leftmost[0][j] is initialized to -INF (no color found to the left)
24        int leftmost[n + 1][3];
25        fill(leftmost[0], leftmost[0] + 3, -INF);
26      
27        // Build leftmost array by traversing from left to right
28        for (int i = 1; i <= n; ++i) {
29            // Copy values from the previous position
30            for (int j = 0; j < 3; ++j) {
31                leftmost[i][j] = leftmost[i - 1][j];
32            }
33            // Update the position for current color
34            leftmost[i][colors[i - 1] - 1] = i - 1;
35        }
36      
37        // Process each query
38        vector<int> result;
39        for (auto& query : queries) {
40            int index = query[0];
41            int targetColor = query[1] - 1;  // Convert to 0-indexed
42          
43            // Calculate distance to nearest target color on the left and right
44            int distanceToLeft = index - leftmost[index + 1][targetColor];
45            int distanceToRight = rightmost[index][targetColor] - index;
46          
47            // Find minimum distance
48            int minDistance = min(distanceToLeft, distanceToRight);
49          
50            // If distance is greater than array size, color doesn't exist
51            result.push_back(minDistance > n ? -1 : minDistance);
52        }
53      
54        return result;
55    }
56};
57
1function shortestDistanceColor(colors: number[], queries: number[][]): number[] {
2    const n = colors.length;
3    const INF = 1 << 30; // Large value representing infinity
4  
5    // right[i][c] stores the nearest index >= i where color c+1 appears
6    // Initialize with INF (no color found)
7    const right: number[][] = Array(n + 1)
8        .fill(0)
9        .map(() => Array(3).fill(INF));
10  
11    // left[i][c] stores the nearest index < i where color c+1 appears
12    // Initialize with -INF (no color found)
13    const left: number[][] = Array(n + 1)
14        .fill(0)
15        .map(() => Array(3).fill(-INF));
16  
17    // Build right array: scan from right to left
18    // For each position, inherit values from next position
19    // and update current color's position
20    for (let i = n - 1; i >= 0; --i) {
21        // Copy values from next position
22        for (let j = 0; j < 3; ++j) {
23            right[i][j] = right[i + 1][j];
24        }
25        // Update current color's position (colors are 1-indexed)
26        right[i][colors[i] - 1] = i;
27    }
28  
29    // Build left array: scan from left to right
30    // For each position, inherit values from previous position
31    // and update current color's position
32    for (let i = 1; i <= n; ++i) {
33        // Copy values from previous position
34        for (let j = 0; j < 3; ++j) {
35            left[i][j] = left[i - 1][j];
36        }
37        // Update current color's position (colors are 1-indexed)
38        left[i][colors[i - 1] - 1] = i - 1;
39    }
40  
41    // Process each query
42    const result: number[] = [];
43    for (const [index, color] of queries) {
44        // Calculate minimum distance to the target color
45        // Distance to left occurrence: index - left[index + 1][color - 1]
46        // Distance to right occurrence: right[index][color - 1] - index
47        const distance = Math.min(
48            index - left[index + 1][color - 1], 
49            right[index][color - 1] - index
50        );
51      
52        // If distance is greater than array length, color doesn't exist
53        result.push(distance > n ? -1 : distance);
54    }
55  
56    return result;
57}
58

Time and Space Complexity

The time complexity is O(n + q), where n is the length of the array colors and q is the number of queries.

  • Building the right array: O(n × 3) = O(n) - iterating through n elements, updating 3 color positions each time
  • Building the left array: O(n × 3) = O(n) - iterating through n elements, updating 3 color positions each time
  • Processing queries: O(q) - each query takes O(1) time to compute the minimum distance
  • Total: O(n + n + q) = O(n + q)

The space complexity is O(n).

  • right array: O((n + 1) × 3) = O(n) - storing 3 color positions for each of n + 1 positions
  • left array: O((n + 1) × 3) = O(n) - storing 3 color positions for each of n + 1 positions
  • ans array: O(q) - storing results for q queries
  • Total: O(n + n + q) = O(n + q)

However, if we consider the output array ans as part of the required output rather than auxiliary space, the space complexity becomes O(n), which matches the reference answer. The reference answer likely considers only the auxiliary space used by the left and right arrays.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Errors in Index Mapping

One of the most common mistakes in this solution is confusion between the 0-indexed array positions and the 1-indexed left array. The left array uses 1-based indexing to simplify query processing, but this can easily lead to errors.

Pitfall Example:

# WRONG: Using left[i] instead of left[i + 1]
distance_to_left = query_position - nearest_left[query_position][target_color_idx]

Why it's wrong: The left[i] stores information about positions up to i-1. To get information about positions up to and including query_position, you need left[query_position + 1].

Correct Solution:

# CORRECT: Use left[i + 1] for query at position i
distance_to_left = query_position - nearest_left[query_position + 1][target_color_idx]

2. Color Index Conversion Mistakes

The colors are 1-indexed (1, 2, 3) but arrays are 0-indexed, requiring careful conversion.

Pitfall Example:

# WRONG: Forgetting to subtract 1 from color value
nearest_right[position][colors[position]] = position  # colors[position] could be 3!

Why it's wrong: This would cause an IndexError when colors[position] = 3 since valid indices are 0, 1, 2.

Correct Solution:

# CORRECT: Convert 1-based color to 0-based index
current_color = colors[position] - 1
nearest_right[position][current_color] = position

3. Incorrect Boundary Value Initialization

Using inappropriate sentinel values can lead to incorrect distance calculations.

Pitfall Example:

# WRONG: Using 0 or -1 as sentinel values
nearest_left = [[-1] * 3 for _ in range(n + 1)]

Why it's wrong: Using -1 or 0 as sentinel values will produce incorrect distances. For example, if no color exists to the left, query_position - (-1) would give query_position + 1, which might seem like a valid distance.

Correct Solution:

# CORRECT: Use infinity values to ensure non-existent colors produce large distances
nearest_left = [[float('-inf')] * 3 for _ in range(n + 1)]
nearest_right = [[float('inf')] * 3 for _ in range(n + 1)]

4. Incorrect Distance Validation

Determining whether a color exists requires careful threshold checking.

Pitfall Example:

# WRONG: Using wrong threshold or comparison
if min_distance >= n:  # Should be > not >=
    result.append(-1)

Why it's wrong: A distance equal to n-1 is valid (from index 0 to index n-1), so using >= would incorrectly mark some valid distances as non-existent.

Correct Solution:

# CORRECT: Distance exceeding n means color doesn't exist
if min_distance > n:
    result.append(-1)

5. Not Handling Edge Cases Properly

Failing to handle queries at array boundaries correctly.

Pitfall Example:

# WRONG: Not checking array bounds for queries
for query_position, target_color in queries:
    # Directly accessing without validation
    distance_to_right = nearest_right[query_position][target_color_idx] - query_position

Why it's wrong: While the problem typically ensures valid query positions, defensive programming should validate that 0 <= query_position < n.

Correct Solution:

# Add validation if needed
for query_position, target_color in queries:
    if query_position < 0 or query_position >= n:
        result.append(-1)
        continue
    # Process valid query...
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More