Python 3208.py
class Solution:
def numberOfAlternatingGroups(self, colors: List[int], k: int) -> int:
result = 0
size = len(colors)
# length of alternating group at previous position
previous = 1
# and at current position
current = 0
# I use two loops and an if to avoid slow modulo and keep memory consumption low
# the inner part of each loop (and the if) is almost identical
# same cycle
for a, b in zip(colors, colors[1 : ]):
current = (a != b) * previous + 1 # same as 1 if a == b else previous + 1
result += current >= k
previous = current
# wraparound
if k >= 1:
current = 1 if colors[ 0 ] == colors[ -1 ] else previous + 1
result += current >= k
previous = current
# second cycle
for a, b in zip(colors, colors[1 : k - 1]):
current = (a != b) * previous + 1
result += current >= k
previous = current
return result
Difficulty
and has an acceptance rate of about 60% (200k+ submissions).
Typical patterns to solve this problem:
- Array
- Sliding Window
AI Disclaimer
However, two years later I set up this website and used various AI tools to generate most of the walkthrough / explanations below.
See this dedicated page for more details.
Code Walkthrough
Overview
The provided implementation computes the number of contiguous sub-segments of length k in a circular binary array where adjacent elements strictly alternate. It treats the input list as a ring, so the segment may wrap from the last element back to the first. The algorithm walks the array twice, maintaining the length of the current alternating run and incrementally counting qualifying groups.
Core Logic
Initialization
result = 0
size = len(colors)
previous = 1
current = 0
result accumulates the answer. previous stores the length of the alternating run that ends at the element processed in the prior iteration. The initial value of previous is set to 1 because a single element always forms a run of length 1.
First forward pass
for a, b in zip(colors, colors[1:]):
current = (a != b) * previous + 1
result += current >= k
previous = current
The loop iterates over each adjacent pair of elements. The expression (a != b) * previous + 1 yields previous + 1 when the pair differs (continuing the alternating pattern) and 1 when they are equal (starting a new run). The boolean multiplication avoids an explicit if. After computing current, the code adds one to result if the run length meets or exceeds k, then shifts previous forward.
Handling the wraparound edge
if k >= 1:
current = 1 if colors[0] == colors[-1] else previous + 1
result += current >= k
previous = current
Because the array is circular, the relationship between the last and first elements must be examined. When k is at least 1, the code checks whether the first and last colors match. If they do, the alternating run resets to 1; otherwise it extends the previous run. The same counting logic as in the forward pass is applied.
Second pass for the remaining positions
for a, b in zip(colors, colors[1:k-1]):
current = (a != b) * previous + 1
result += current >= k
previous = current
After the wraparound step, the algorithm needs to consider the initial k-1 positions again to capture groups that start near the end of the list and wrap forward. This second loop processes exactly those pairs, reusing the same update formula and counting condition.
Final result
return result
The accumulated result is returned as the total number of alternating groups of length k in the circular array.
Edge Cases Addressed
The code explicitly checks if k >= 1 before accessing the first and last elements, ensuring that the wraparound logic is only executed when a group of at least one element is meaningful. The two-pass structure guarantees that groups spanning the boundary are counted, even when k is larger than the number of distinct alternating segments in the linear traversal. By resetting previous appropriately after each segment, the algorithm correctly handles cases where consecutive equal colors break the alternating pattern.
Performance Optimizations
The implementation avoids the overhead of modular arithmetic for circular indexing by performing a dedicated wraparound step instead of using % size inside the main loop. This reduces the number of expensive remainder operations. Boolean multiplication (a != b) * previous replaces a conditional branch, allowing the Python interpreter to execute a single arithmetic expression per iteration. Using zip to iterate over adjacent pairs eliminates the need for explicit index management and keeps the memory footprint low, as no auxiliary arrays are allocated. The algorithm processes each element a constant number of times, resulting in linear time behavior with minimal auxiliary storage.
ELI5 - Simplified Explanation
Imagine a long necklace made of colorful beads arranged in a circle. Some beads are red and some are blue. An alternating group happens when the colors switch back and forth like red, blue, red, blue, all the way around the necklace. The goal is to count how many spots on this necklace have a long enough chain of these switching colors.
The code starts by looking at the beads one by one. It keeps track of how many colors have already successfully switched. If a bead is a different color than the one before it, the chain gets longer. If two beads next to each other are the same color, the chain has to start over and be reset.
Because the necklace is a circle, the end connects back to the beginning. The code carefully checks this connection point to make sure it does not miss any groups that might wrap around the circle. It keeps walking around the beads just far enough to finish checking every single possibility. Whenever the chain of alternating colors reaches the length needed for the group, a counter goes up by one. After checking every spot, the computer knows exactly how many of these special groups are hidden on the necklace.
Code Quality
Complexity Analysis
As a rule of thumb, the maximum input size for one second execution time is
- factorial: up to 10 elements
- exponential: up to 20 elements
- polynomial: a few thousand elements
- loglinear: up to 100,000 elements
- linear: a few million elements
- logarithmic: a billion elements
- constant: practically unlimited
Other Solutions
Obviously, I did that after I solved the problem myself.
In general, AlgoMonster and WalkCCC solutions are high-quality and always worth a look.
I haven't checked most of the other solutions, but they might be helpful as well.
And of course, the LeetCode forum is always a good place (or even the best) to look for alternative approaches.
(click on a column header to sort the table)
| author | |||||||
|---|---|---|---|---|---|---|---|
| author | solutions | ||||||
| www | AlgoMonster | Python | Java | C++ | TS | ||
| WalkCCC | Python | Java | C++ | ||||
| leetcode.ca | Python | Java | C++ | Go | TS | ||
| Progiez | Python | Java | C++ | ||||
| github | charles-wangkai | Java | |||||
| fsq | Python | ||||||
| JoshCrozier | JS | ||||||
| AlgoMonster | C++, Python, Java, TypeScript | ||||||
| WalkCCC | C++, Python, Java | ||||||
| leetcode.ca | C++, Go, Java, Python, TypeScript | ||||||
| Progiez | C++, Java, Python | ||||||
| charles-wangkai | Java | ||||||
| fsq | Python | ||||||
| JoshCrozier | JavaScript |
Videos
Solved On The Same Day
- problem 3210: Find the Encrypted String
- problem 3067: Count Pairs of Connectable Servers in a Weighted Tree Network
- problem 3212: Count Submatrices With Equal Frequency of X and Y
- problem 2897: Apply Operations on Array to Maximize Sum of Squares
- problem 3154: Find Number of Ways to Reach the K-th Stair
Extra
- Tech Stack - my development environment and tools
- AI - how AI helped me create this website (NOT solving the problems)
- Progress - progression over time
- Quality - code quality