3206. Alternating Groups I
Problem Description
You have a circle of tiles where each tile is either red or blue. The tiles are represented by an array colors
where:
colors[i] == 0
means tilei
is redcolors[i] == 1
means tilei
is blue
An alternating group is defined as any sequence of exactly 3 consecutive tiles in the circle where the colors alternate (meaning the middle tile has a different color from both its left and right neighbors).
Your task is to count the total number of alternating groups in the circle.
Since the tiles form a circle, the first and last tiles in the array are considered adjacent to each other. This means you need to check for alternating groups that wrap around from the end of the array back to the beginning.
For example, if you have colors = [0, 1, 0, 1]
:
- Tiles at positions 0, 1, 2 form group
[0, 1, 0]
- this alternates (red-blue-red) - Tiles at positions 1, 2, 3 form group
[1, 0, 1]
- this alternates (blue-red-blue) - Tiles at positions 2, 3, 0 form group
[0, 1, 0]
- this alternates (red-blue-red) - Tiles at positions 3, 0, 1 form group
[1, 0, 1]
- this alternates (blue-red-blue)
So there would be 4 alternating groups in total.
The solution uses a clever approach of virtually "unfolding" the circle by iterating through 2n
positions (where n
is the length of the array), using modulo arithmetic to wrap around. It maintains a counter cnt
to track the current sequence length of alternating colors. When the same color appears consecutively, the counter resets to 1. Once we've passed position n
and have a valid alternating sequence of length 3 or more, we count it as an alternating group.
Intuition
The key insight is recognizing that counting groups in a circle can be tricky due to the wraparound nature. When we check for alternating groups at the end of the array, we need to consider elements from the beginning of the array as well.
To handle this circular structure elegantly, we can imagine "unrolling" the circle into a straight line by duplicating the array. Instead of dealing with complex wraparound logic, we traverse through 2n
positions where n
is the array length. This way, every possible group of 3 consecutive tiles in the original circle appears as a regular consecutive sequence somewhere in our extended traversal.
The next observation is that we don't actually need to store this doubled array. We can simply use modulo arithmetic (i % n
) to access the correct position in the original array as we iterate through 2n
positions.
As we traverse, we maintain a running count (cnt
) of how many consecutive tiles we've seen that alternate in color. Whenever we encounter two adjacent tiles of the same color, we know any alternating sequence is broken, so we reset cnt
to 1 (starting a new potential sequence). Otherwise, we increment cnt
because the alternating pattern continues.
The clever part is knowing when to count a valid group. We only start counting after we've passed position n
(meaning we're in the "second half" of our virtual doubled array). This ensures we don't count the same group twice. When cnt >= 3
and we're past position n
, we've found a valid alternating group of size 3.
This approach transforms a circular problem into a linear one, making it much simpler to solve while avoiding the pitfall of counting groups multiple times.
Learn more about Sliding Window patterns.
Solution Approach
The implementation follows a single-pass algorithm that simulates traversing a circular array by iterating through 2n
positions.
Initial Setup:
- Set
k = 3
to represent the required length of an alternating group - Get the array length
n = len(colors)
- Initialize
ans = 0
to count valid alternating groups - Initialize
cnt = 0
to track the current alternating sequence length
Main Loop:
The algorithm iterates through 2n
positions (from 0
to 2n-1
):
for i in range(n << 1): # n << 1 is equivalent to n * 2
Checking for Alternating Pattern:
At each position i
, we check if the current color matches the previous color:
if i and colors[i % n] == colors[(i - 1) % n]: cnt = 1 # Reset counter if same color found else: cnt += 1 # Increment counter if colors alternate
The condition i and
ensures we skip the check when i = 0
(no previous element to compare).
Counting Valid Groups:
After updating cnt
, we check if we've found a valid alternating group:
ans += i >= n and cnt >= k
This line adds 1 to ans
when both conditions are met:
i >= n
: We're in the second half of our virtual doubled array (avoiding duplicate counts)cnt >= k
: We have an alternating sequence of at least length 3
The expression i >= n and cnt >= k
evaluates to True
(1) or False
(0), which is then added to ans
.
Why This Works:
By iterating through 2n
positions, every group of 3 consecutive tiles in the circle appears exactly once as a valid sequence when i >= n
. The modulo operation i % n
handles the circular wraparound naturally, and the counter cnt
efficiently tracks alternating sequences without needing to store or check multiple elements at once.
The time complexity is O(n)
with a single pass through 2n
elements, and space complexity is O(1)
as we only use a few variables regardless of input size.
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 the solution with colors = [1, 0, 1, 0, 0]
.
We're looking for groups of 3 consecutive tiles where colors alternate. Since it's a circle, position 4 wraps around to position 0.
Initial State:
n = 5
(array length)k = 3
(required group size)ans = 0
(count of alternating groups)cnt = 0
(current alternating sequence length)
Iteration through 2n = 10 positions:
i | i%5 | colors[i%5] | Previous | cnt | i >= n? | cnt >= k? | ans |
---|---|---|---|---|---|---|---|
0 | 0 | 1 (blue) | - | 1 | No | No | 0 |
1 | 1 | 0 (red) | 1 | 2 | No | No | 0 |
2 | 2 | 1 (blue) | 0 | 3 | No | Yes | 0 |
3 | 3 | 0 (red) | 1 | 4 | No | Yes | 0 |
4 | 4 | 0 (red) | 0 | 1 | No | No | 0 |
5 | 0 | 1 (blue) | 0 | 2 | Yes | No | 0 |
6 | 1 | 0 (red) | 1 | 3 | Yes | Yes | 1 |
7 | 2 | 1 (blue) | 0 | 4 | Yes | Yes | 2 |
8 | 3 | 0 (red) | 1 | 5 | Yes | Yes | 3 |
9 | 4 | 0 (red) | 0 | 1 | Yes | No | 3 |
Key moments:
- At i=4: colors[4]=0 equals colors[3]=0, so cnt resets to 1
- At i=6: First valid group counted (positions 0→1→2: [1,0,1])
- At i=7: Second group counted (positions 1→2→3: [0,1,0])
- At i=8: Third group counted (positions 2→3→4: [1,0,0])... wait, this isn't alternating!
Actually, let me recalculate position 8:
- At i=8: We're checking positions 2→3→4, but colors[3]=0 and colors[4]=0 are the same
- So the group [1,0,0] is NOT alternating
The algorithm correctly identifies this because when we reach i=9 (checking position 4), we see colors[4]=0 equals colors[3]=0, resetting cnt to 1.
Final Result: The algorithm finds 3 alternating groups:
- Positions 0,1,2: colors [1,0,1] ✓
- Positions 1,2,3: colors [0,1,0] ✓
- Positions [2,3,4]: colors [1,0,0] ✗ (not alternating)
Actually, let me trace through this more carefully:
When i=8, we're at position 3 (i%5=3). We compare colors[3]=0 with colors[2]=1. They're different, so cnt becomes 5. But this doesn't mean positions 2,3,4 form an alternating group - it means we have 5 consecutive alternating colors in our virtual doubled array.
The algorithm counts a group whenever cnt>=3 and i>=n. At i=8, positions 6,7,8 in the virtual array (which map to real positions 1,2,3) form an alternating group.
The final answer is 3 alternating groups:
- [3,4,0]: [0,0,1] - This is NOT alternating, so it shouldn't be counted
Let me verify: The algorithm correctly identifies that at position 4 and 3, we have the same color (0), which breaks the alternating pattern and resets cnt to 1 at i=4 and again at i=9.
Solution Implementation
1class Solution:
2 def numberOfAlternatingGroups(self, colors: List[int]) -> int:
3 # Group size we're looking for
4 group_size = 3
5
6 # Length of the colors array
7 array_length = len(colors)
8
9 # Initialize result counter and current alternating sequence length
10 result = 0
11 current_sequence_length = 0
12
13 # Iterate twice through the array to handle circular nature
14 # This ensures we check all possible groups that wrap around
15 for index in range(array_length * 2):
16 # Get the actual position in the circular array
17 current_pos = index % array_length
18 previous_pos = (index - 1) % array_length
19
20 # Check if current element breaks the alternating pattern
21 if index > 0 and colors[current_pos] == colors[previous_pos]:
22 # Reset sequence length if pattern is broken
23 current_sequence_length = 1
24 else:
25 # Extend the alternating sequence
26 current_sequence_length += 1
27
28 # Count valid groups only in the second pass to avoid duplicates
29 # A valid group exists if we're in the second iteration and
30 # the current sequence is at least as long as the required group size
31 if index >= array_length and current_sequence_length >= group_size:
32 result += 1
33
34 return result
35
1class Solution {
2 public int numberOfAlternatingGroups(int[] colors) {
3 // Group size we're looking for
4 int groupSize = 3;
5
6 // Length of the colors array
7 int arrayLength = colors.length;
8
9 // Total count of valid alternating groups
10 int alternatingGroupCount = 0;
11
12 // Current consecutive alternating elements count
13 int currentAlternatingLength = 0;
14
15 // Iterate twice through the array to handle circular nature
16 // This ensures we check all possible groups that wrap around
17 for (int i = 0; i < arrayLength * 2; i++) {
18 // Get current index in the circular array
19 int currentIndex = i % arrayLength;
20 int previousIndex = (i - 1) % arrayLength;
21
22 // Check if current element breaks the alternating pattern
23 if (i > 0 && colors[currentIndex] == colors[previousIndex]) {
24 // Reset counter when two adjacent elements have same color
25 currentAlternatingLength = 1;
26 } else {
27 // Continue the alternating sequence
28 currentAlternatingLength++;
29 }
30
31 // Only count groups in the second pass to avoid duplicates
32 // If we have enough alternating elements, we found a valid group
33 if (i >= arrayLength && currentAlternatingLength >= groupSize) {
34 alternatingGroupCount++;
35 }
36 }
37
38 return alternatingGroupCount;
39 }
40}
41
1class Solution {
2public:
3 int numberOfAlternatingGroups(vector<int>& colors) {
4 // Group size we're looking for
5 const int groupSize = 3;
6
7 // Get the size of the colors array
8 int n = colors.size();
9
10 // Initialize result counter and current alternating sequence length
11 int result = 0;
12 int currentSequenceLength = 0;
13
14 // Iterate twice through the array to handle circular nature
15 // This allows us to count groups that wrap around the end
16 for (int i = 0; i < n * 2; ++i) {
17 // Get current and previous indices (with circular wrapping)
18 int currentIndex = i % n;
19 int previousIndex = (i - 1) % n;
20
21 // Check if current color is same as previous color
22 if (i > 0 && colors[currentIndex] == colors[previousIndex]) {
23 // Same color found, reset sequence to 1
24 currentSequenceLength = 1;
25 } else {
26 // Colors are different (alternating), increment sequence length
27 ++currentSequenceLength;
28 }
29
30 // Only count valid groups in the second pass (i >= n)
31 // to avoid double counting
32 if (i >= n && currentSequenceLength >= groupSize) {
33 ++result;
34 }
35 }
36
37 return result;
38 }
39};
40
1/**
2 * Counts the number of alternating groups of size k in a circular array
3 * @param colors - Array of colors represented as numbers (0 or 1)
4 * @returns The number of valid alternating groups
5 */
6function numberOfAlternatingGroups(colors: number[]): number {
7 // Size of the alternating group to find
8 const groupSize: number = 3;
9
10 // Length of the colors array
11 const arrayLength: number = colors.length;
12
13 // Initialize result counter and current alternating sequence length
14 let result: number = 0;
15 let currentSequenceLength: number = 0;
16
17 // Iterate through the array twice to handle circular nature
18 // We check each position as if the array wraps around
19 for (let i = 0; i < arrayLength * 2; i++) {
20 // Get current and previous indices in circular array
21 const currentIndex: number = i % arrayLength;
22 const previousIndex: number = (i - 1) % arrayLength;
23
24 // Check if current color is same as previous color
25 if (i > 0 && colors[currentIndex] === colors[previousIndex]) {
26 // Reset sequence length if colors are same (not alternating)
27 currentSequenceLength = 1;
28 } else {
29 // Increment sequence length if colors are alternating
30 currentSequenceLength++;
31 }
32
33 // Count valid groups only in the second pass (i >= arrayLength)
34 // and when we have at least groupSize alternating elements
35 if (i >= arrayLength && currentSequenceLength >= groupSize) {
36 result++;
37 }
38 }
39
40 return result;
41}
42
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array colors
. The algorithm iterates through n << 1
(which equals 2n
) elements in a single loop. Since 2n
is still linear with respect to n
, the overall time complexity remains O(n)
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for variables k
, n
, ans
, cnt
, and i
, regardless of the input size. No additional data structures that scale with the input are created.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting the Circular Nature and Missing Wraparound Groups
One of the most common mistakes is treating the array as linear instead of circular, which causes the algorithm to miss groups that wrap around from the end to the beginning.
Incorrect approach:
def numberOfAlternatingGroups(self, colors: List[int]) -> int:
result = 0
n = len(colors)
# Only checking linear groups, missing wraparound cases
for i in range(n):
if i + 2 < n: # This condition prevents checking wraparound groups!
if colors[i] != colors[i+1] and colors[i+1] != colors[i+2]:
result += 1
return result
Why it fails: This approach only checks groups that fit entirely within the array bounds, missing groups like [colors[n-2], colors[n-1], colors[0]]
and [colors[n-1], colors[0], colors[1]]
.
Solution: Use modulo arithmetic to handle wraparound naturally:
for i in range(n):
if colors[i] != colors[(i+1) % n] and colors[(i+1) % n] != colors[(i+2) % n]:
result += 1
2. Double-Counting Groups When Simulating the Circle
When iterating through 2n
positions to simulate the circle, it's easy to count the same group multiple times if you're not careful about when to start counting.
Incorrect approach:
def numberOfAlternatingGroups(self, colors: List[int]) -> int:
result = 0
cnt = 0
n = len(colors)
for i in range(n * 2):
# Update cnt logic...
# Wrong: Counting from the beginning causes duplicates
if cnt >= 3: # Missing the i >= n check!
result += 1
return result
Why it fails: Each valid group gets counted twice - once in the first n
iterations and once in the second n
iterations.
Solution: Only count groups after passing position n
:
if i >= n and cnt >= 3: result += 1
3. Incorrect Sequence Length Tracking
Another pitfall is incorrectly managing the alternating sequence counter, especially at the boundaries or when resetting.
Incorrect approach:
def numberOfAlternatingGroups(self, colors: List[int]) -> int:
cnt = 1 # Starting with 1 instead of 0
# ... or ...
if colors[i % n] == colors[(i - 1) % n]:
cnt = 0 # Resetting to 0 instead of 1
Why it fails:
- Starting
cnt
at 1 gives incorrect counts for the first element - Resetting to 0 when finding consecutive same colors means you're not counting the current element as the start of a new potential sequence
Solution: Initialize cnt = 0
and reset to 1
when the pattern breaks:
cnt = 0 # Start at 0
for i in range(n * 2):
if i and colors[i % n] == colors[(i - 1) % n]:
cnt = 1 # Reset to 1, not 0
else:
cnt += 1
4. Off-by-One Errors in Group Size Validation
Confusing whether to check for cnt == 3
or cnt >= 3
can lead to missing valid groups, especially when the alternating sequence is longer than 3.
Incorrect approach:
if i >= n and cnt == 3: # Wrong: only counts exactly 3 result += 1
Why it fails: A sequence like [0, 1, 0, 1]
has an alternating length of 4, which contains 2 valid groups of size 3: [0,1,0]
and [1,0,1]
. Using cnt == 3
would miss the second group.
Solution: Use cnt >= k
to count all valid positions in longer alternating sequences:
if i >= n and cnt >= group_size: result += 1
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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
Want a Structured Path to Master System Design Too? Don’t Miss This!