Facebook Pixel

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 tile i is red
  • colors[i] == 1 means tile i 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.

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

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 Evaluator

Example 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:

ii%5colors[i%5]Previouscnti >= n?cnt >= k?ans
001 (blue)-1NoNo0
110 (red)12NoNo0
221 (blue)03NoYes0
330 (red)14NoYes0
440 (red)01NoNo0
501 (blue)02YesNo0
610 (red)13YesYes1
721 (blue)04YesYes2
830 (red)15YesYes3
940 (red)01YesNo3

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:

  1. Positions 0,1,2: colors [1,0,1] ✓
  2. Positions 1,2,3: colors [0,1,0] ✓
  3. 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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

Load More