2672. Number of Adjacent Elements With the Same Color
Problem Description
You start with an array of length n
where all elements are initially 0 (representing uncolored positions). You're given a series of queries, where each query contains an index and a color value.
For each query [index, color]
, you need to:
- Color the element at the given index with the specified color (set
colors[index] = color
) - Count how many adjacent pairs in the entire array have the same color after this coloring
An adjacent pair means two consecutive elements in the array (elements at positions i
and i+1
). Two adjacent elements form a matching pair if they have the same color value, and that color is not 0 (uncolored elements don't count as matching).
The solution tracks the number of matching adjacent pairs efficiently by:
- Before changing a position's color, it checks if that position currently forms matching pairs with its neighbors (left and right), and decreases the count accordingly
- After assigning the new color, it checks if the new color creates matching pairs with its neighbors and increases the count
- The variable
x
maintains the running count of matching adjacent pairs - For each query, the current count is stored in the answer array
For example, if you have an array [1, 1, 2, 0, 0]
, there is exactly 1 matching adjacent pair (the two 1's at positions 0 and 1). If you then color position 2 with color 1, the array becomes [1, 1, 1, 0, 0]
and you now have 2 matching adjacent pairs.
The function returns an array where each element represents the total number of matching adjacent pairs after processing each query in order.
Intuition
The key insight is that when we color a position, we only need to consider how this change affects the adjacent pairs involving that specific position - not the entire array. Each position can form at most two adjacent pairs: one with its left neighbor and one with its right neighbor.
When we update colors[i]
to a new color, we're potentially changing two adjacent pairs:
- The pair
(colors[i-1], colors[i])
- The pair
(colors[i], colors[i+1])
Instead of recounting all adjacent pairs from scratch after each query (which would be O(n) per query), we can maintain a running count and just update it based on the local changes.
The approach is to:
- Remove the old contributions: Before changing
colors[i]
, check if the current value forms any matching pairs with its neighbors. If it does, we need to subtract these from our count since they won't exist after the change. - Add the new contributions: After setting the new color, check if this new color creates any matching pairs with the neighbors and add these to our count.
This incremental update strategy is efficient because:
- We only examine at most 2 positions (the left and right neighbors) per query
- We maintain the global count
x
throughout all queries, updating it incrementally - Each query takes O(1) time instead of O(n)
The careful handling of boundary conditions (when i = 0
or i = n-1
) and checking that colors are non-zero (since 0 represents uncolored) ensures we count only valid matching pairs.
Solution Approach
The solution uses a simulation approach with incremental updates to efficiently track the number of matching adjacent pairs.
Data Structures:
nums
: An array of sizen
initialized with zeros to represent the current state of colorsans
: Result array to store the count of matching pairs after each queryx
: A running counter that maintains the current number of matching adjacent pairs
Algorithm Implementation:
For each query (i, c)
where we need to color position i
with color c
:
-
Remove contributions from the old color at position
i
:- Check left neighbor: If
i > 0
andnums[i] != 0
andnums[i-1] == nums[i]
, decrementx
by 1 - Check right neighbor: If
i < n-1
andnums[i] != 0
andnums[i+1] == nums[i]
, decrementx
by 1
These checks ensure we only decrement when there's an existing matching pair that will be broken.
- Check left neighbor: If
-
Add contributions from the new color
c
:- Check left neighbor: If
i > 0
andnums[i-1] == c
, incrementx
by 1 - Check right neighbor: If
i < n-1
andnums[i+1] == c
, incrementx
by 1
These checks determine if the new color will create matching pairs with neighbors.
- Check left neighbor: If
-
Update and record:
- Store the current value of
x
inans[k]
for the k-th query - Update
nums[i] = c
to reflect the new color
- Store the current value of
The order of operations is crucial: we must update the count before actually changing the color in the array, otherwise we'd lose information about the previous state.
Time Complexity: O(m) where m is the number of queries, since each query processes in O(1) time.
Space Complexity: O(n) for the nums
array and O(m) for the answer array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with n = 5
and queries [[1,2], [2,2], [0,1], [3,1]]
.
Initial State:
- Array:
[0, 0, 0, 0, 0]
- Matching pairs count
x = 0
Query 1: [1, 2] - Color position 1 with color 2
- Check position 1's current contributions:
- Left neighbor (position 0):
nums[1] = 0
, so no existing pair to remove - Right neighbor (position 2):
nums[1] = 0
, so no existing pair to remove
- Left neighbor (position 0):
- Apply new color 2 at position 1:
- Left neighbor check:
nums[0] = 0 ≠ 2
, no new pair - Right neighbor check:
nums[2] = 0 ≠ 2
, no new pair
- Left neighbor check:
- Update:
nums[1] = 2
,x = 0
- Array:
[0, 2, 0, 0, 0]
, Answer:[0]
Query 2: [2, 2] - Color position 2 with color 2
- Check position 2's current contributions:
- Left neighbor (position 1):
nums[2] = 0
, so no existing pair to remove - Right neighbor (position 3):
nums[2] = 0
, so no existing pair to remove
- Left neighbor (position 1):
- Apply new color 2 at position 2:
- Left neighbor check:
nums[1] = 2 = 2
, creates a new pair!x++
- Right neighbor check:
nums[3] = 0 ≠ 2
, no new pair
- Left neighbor check:
- Update:
nums[2] = 2
,x = 1
- Array:
[0, 2, 2, 0, 0]
, Answer:[0, 1]
Query 3: [0, 1] - Color position 0 with color 1
- Check position 0's current contributions:
- No left neighbor (boundary)
- Right neighbor (position 1):
nums[0] = 0
, so no existing pair to remove
- Apply new color 1 at position 0:
- No left neighbor to check
- Right neighbor check:
nums[1] = 2 ≠ 1
, no new pair
- Update:
nums[0] = 1
,x = 1
- Array:
[1, 2, 2, 0, 0]
, Answer:[0, 1, 1]
Query 4: [3, 1] - Color position 3 with color 1
- Check position 3's current contributions:
- Left neighbor (position 2):
nums[3] = 0
, so no existing pair to remove - Right neighbor (position 4):
nums[3] = 0
, so no existing pair to remove
- Left neighbor (position 2):
- Apply new color 1 at position 3:
- Left neighbor check:
nums[2] = 2 ≠ 1
, no new pair - Right neighbor check:
nums[4] = 0 ≠ 1
, no new pair
- Left neighbor check:
- Update:
nums[3] = 1
,x = 1
- Array:
[1, 2, 2, 1, 0]
, Answer:[0, 1, 1, 1]
Final Result: [0, 1, 1, 1]
The array [1, 2, 2, 1, 0]
has exactly one matching adjacent pair: positions 1 and 2 both have color 2.
Solution Implementation
1class Solution:
2 def colorTheArray(self, n: int, queries: List[List[int]]) -> List[int]:
3 # Initialize array with all elements as 0 (uncolored)
4 colors = [0] * n
5
6 # Result array to store count after each query
7 result = [0] * len(queries)
8
9 # Running count of adjacent elements with same non-zero color
10 adjacent_same_color_count = 0
11
12 # Process each query
13 for query_index, (position, new_color) in enumerate(queries):
14 # Remove contribution of current element before updating
15 # Check if current element forms a pair with left neighbor
16 if position > 0 and colors[position] != 0 and colors[position - 1] == colors[position]:
17 adjacent_same_color_count -= 1
18
19 # Check if current element forms a pair with right neighbor
20 if position < n - 1 and colors[position] != 0 and colors[position + 1] == colors[position]:
21 adjacent_same_color_count -= 1
22
23 # Add contribution of new color
24 # Check if new color will form a pair with left neighbor
25 if position > 0 and colors[position - 1] == new_color and new_color != 0:
26 adjacent_same_color_count += 1
27
28 # Check if new color will form a pair with right neighbor
29 if position < n - 1 and colors[position + 1] == new_color and new_color != 0:
30 adjacent_same_color_count += 1
31
32 # Store current count in result
33 result[query_index] = adjacent_same_color_count
34
35 # Update the color at the specified position
36 colors[position] = new_color
37
38 return result
39
1class Solution {
2 public int[] colorTheArray(int n, int[][] queries) {
3 int queryCount = queries.length;
4 int[] colors = new int[n]; // Array to store colors, initially all 0 (uncolored)
5 int[] result = new int[queryCount]; // Result array to store count after each query
6 int adjacentPairs = 0; // Running count of adjacent elements with same color
7
8 for (int queryIndex = 0; queryIndex < queryCount; queryIndex++) {
9 int position = queries[queryIndex][0];
10 int newColor = queries[queryIndex][1];
11
12 // Before changing the color, remove existing adjacent pairs involving this position
13
14 // Check if current position and left neighbor form a pair (both colored and same color)
15 if (position > 0 && colors[position] > 0 && colors[position - 1] == colors[position]) {
16 adjacentPairs--;
17 }
18
19 // Check if current position and right neighbor form a pair (both colored and same color)
20 if (position < n - 1 && colors[position] > 0 && colors[position + 1] == colors[position]) {
21 adjacentPairs--;
22 }
23
24 // After changing the color, add new adjacent pairs involving this position
25
26 // Check if new color will form a pair with left neighbor
27 if (position > 0 && colors[position - 1] == newColor) {
28 adjacentPairs++;
29 }
30
31 // Check if new color will form a pair with right neighbor
32 if (position < n - 1 && colors[position + 1] == newColor) {
33 adjacentPairs++;
34 }
35
36 // Store the current count of adjacent pairs
37 result[queryIndex] = adjacentPairs;
38
39 // Apply the new color to the position
40 colors[position] = newColor;
41 }
42
43 return result;
44 }
45}
46
1class Solution {
2public:
3 vector<int> colorTheArray(int n, vector<vector<int>>& queries) {
4 // Initialize array with all elements as 0 (uncolored)
5 vector<int> colors(n, 0);
6
7 // Store results for each query
8 vector<int> result;
9
10 // Track the current count of adjacent pairs with same color
11 int adjacentPairCount = 0;
12
13 // Process each query
14 for (const auto& query : queries) {
15 int index = query[0];
16 int newColor = query[1];
17
18 // Before coloring, check if current position creates any adjacent pairs
19 // We need to subtract these pairs from the count
20
21 // Check left neighbor: if current element and left neighbor both have color and match
22 if (index > 0 && colors[index] > 0 && colors[index - 1] == colors[index]) {
23 adjacentPairCount--;
24 }
25
26 // Check right neighbor: if current element and right neighbor both have color and match
27 if (index < n - 1 && colors[index] > 0 && colors[index + 1] == colors[index]) {
28 adjacentPairCount--;
29 }
30
31 // After coloring with new color, check if new adjacent pairs are formed
32 // We need to add these new pairs to the count
33
34 // Check if new color matches left neighbor
35 if (index > 0 && colors[index - 1] == newColor) {
36 adjacentPairCount++;
37 }
38
39 // Check if new color matches right neighbor
40 if (index < n - 1 && colors[index + 1] == newColor) {
41 adjacentPairCount++;
42 }
43
44 // Store the current count of adjacent pairs after this query
45 result.push_back(adjacentPairCount);
46
47 // Apply the new color to the current position
48 colors[index] = newColor;
49 }
50
51 return result;
52 }
53};
54
1/**
2 * Colors an array based on queries and tracks adjacent elements with same colors
3 * @param n - The size of the array to color
4 * @param queries - Array of [index, color] pairs representing coloring operations
5 * @returns Array containing count of adjacent same-colored pairs after each query
6 */
7function colorTheArray(n: number, queries: number[][]): number[] {
8 // Initialize array with all elements as 0 (uncolored)
9 const colorArray: number[] = new Array(n).fill(0);
10
11 // Store results for each query
12 const results: number[] = [];
13
14 // Track count of adjacent elements with same non-zero colors
15 let adjacentPairsCount: number = 0;
16
17 // Process each query
18 for (const [index, newColor] of queries) {
19 // Before coloring, check if current position creates any adjacent pairs
20
21 // Check left neighbor: if current element and left neighbor both have same non-zero color
22 if (index > 0 && colorArray[index] > 0 && colorArray[index - 1] === colorArray[index]) {
23 // Removing this pair since we're about to change the color
24 adjacentPairsCount--;
25 }
26
27 // Check right neighbor: if current element and right neighbor both have same non-zero color
28 if (index < n - 1 && colorArray[index] > 0 && colorArray[index + 1] === colorArray[index]) {
29 // Removing this pair since we're about to change the color
30 adjacentPairsCount--;
31 }
32
33 // After coloring, check if new color creates any adjacent pairs
34
35 // Check if new color matches left neighbor
36 if (index > 0 && colorArray[index - 1] === newColor) {
37 // Adding a new adjacent pair
38 adjacentPairsCount++;
39 }
40
41 // Check if new color matches right neighbor
42 if (index < n - 1 && colorArray[index + 1] === newColor) {
43 // Adding a new adjacent pair
44 adjacentPairsCount++;
45 }
46
47 // Record current count of adjacent pairs
48 results.push(adjacentPairsCount);
49
50 // Apply the new color to the array
51 colorArray[index] = newColor;
52 }
53
54 return results;
55}
56
Time and Space Complexity
Time Complexity: O(m)
where m
is the length of queries.
The algorithm iterates through each query exactly once. For each query, it performs a constant number of operations:
- Checking and updating at most 2 adjacent positions (left and right neighbors)
- Each check involves simple comparisons and arithmetic operations (
O(1)
) - Updating the array at position
i
and the answer array
Since we process m
queries and each query takes O(1)
time, the total time complexity is O(m)
.
Space Complexity: O(n + m)
where n
is the size of the array and m
is the length of queries.
The space usage consists of:
nums
array:O(n)
space to store the current state of the arrayans
array:O(m)
space to store the result for each query- Variable
x
:O(1)
space for tracking the current count of adjacent same-color pairs - Loop variables:
O(1)
space
The total auxiliary space used is O(n + m)
. If we only consider extra space beyond the output array ans
, then the auxiliary space complexity would be O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Updating the Array Before Adjusting the Count
One of the most common mistakes is updating colors[position] = new_color
before calculating the changes to the adjacent pair count. This destroys the information about the previous color, making it impossible to correctly determine which pairs need to be removed from the count.
Incorrect approach:
# WRONG: Updating color first colors[position] = new_color # This loses the old color information! # Now we can't properly check what pairs existed before if position > 0 and old_color_we_lost == colors[position - 1]: adjacent_same_color_count -= 1
Correct approach:
# RIGHT: Calculate changes first, then update # Check and remove old pairs if position > 0 and colors[position] != 0 and colors[position - 1] == colors[position]: adjacent_same_color_count -= 1 # ... other checks ... # Update color AFTER all calculations colors[position] = new_color
2. Forgetting to Check for Zero (Uncolored) Values
Another critical mistake is not properly handling uncolored elements (value 0). Uncolored elements should never contribute to the matching pair count, even if two adjacent elements are both 0.
Incorrect approach:
# WRONG: Not checking if color is 0 if position > 0 and colors[position - 1] == new_color: adjacent_same_color_count += 1 # This would count pairs of zeros!
Correct approach:
# RIGHT: Ensure we're not counting zero-colored pairs if position > 0 and colors[position - 1] == new_color and new_color != 0: adjacent_same_color_count += 1
3. Double Counting or Missing Pairs When a Position is Recolored
When a position that already has a color is being recolored with the same color, you need to be careful not to double-count existing pairs or incorrectly remove pairs that should remain.
Example scenario: If position i
has color 2, and both neighbors also have color 2, recoloring position i
with color 2 again shouldn't change the count. The implementation handles this correctly by first removing the existing pairs (subtracting 2) and then adding them back (adding 2), resulting in no net change.
4. Incorrect Boundary Checking
Failing to properly check array boundaries can lead to index out of bounds errors or incorrect counts at the edges of the array.
Incorrect approach:
# WRONG: Not checking boundaries if colors[position - 1] == colors[position]: # Could fail when position = 0 adjacent_same_color_count -= 1
Correct approach:
# RIGHT: Always check boundaries first if position > 0 and colors[position - 1] == colors[position]: adjacent_same_color_count -= 1
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
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 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
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 represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!