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 is1
,2
, or3
)
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 index1
- The
3
s are at indices4
,7
, and8
- 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.
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
whereright[i][j]
stores the index of the nearest occurrence of colorj+1
at or to the right of positioni
left
: A 2D array of size(n+1) Ă 3
whereleft[i][j]
stores the index of the nearest occurrence of colorj+1
at or to the left of positioni-1
Implementation Steps:
-
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)
- Set
-
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 fromi+1
- Then update the position for the current color at index
i
- For each position
-
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
- Note:
-
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
- Distance to nearest
- Take the minimum of these two distances
- If the distance exceeds
n
, the color doesn't exist, return-1
- For query
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 EvaluatorExample 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): Initializeright[4]
with[inf, inf, inf]
left
array (size 5Ă3): Initializeleft[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]
- Copy from right[4]:
-
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]
- Copy from right[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]
- Copy from right[2]:
-
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]
- Copy from right[1]:
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]
- Copy from left[0]:
-
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]
- Copy from left[1]:
-
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]
- Copy from left[2]:
-
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]
- Copy from left[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 throughn
elements, updating 3 color positions each time - Building the
left
array:O(n Ă 3) = O(n)
- iterating throughn
elements, updating 3 color positions each time - Processing queries:
O(q)
- each query takesO(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 ofn + 1
positionsleft
array:O((n + 1) Ă 3) = O(n)
- storing 3 color positions for each ofn + 1
positionsans
array:O(q)
- storing results forq
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...
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Donât Miss This!