2550. Count Collisions of Monkeys on a Polygon
Problem Description
You have a regular convex polygon with n
vertices labeled from 0
to n - 1
in clockwise order. Each vertex has exactly one monkey on it.
At the same moment, every monkey moves to one of its neighboring vertices (either clockwise or counterclockwise). A collision occurs when:
- Two or more monkeys end up at the same vertex after moving, OR
- Two monkeys cross paths while moving along an edge
Your task is to find the number of ways the monkeys can move such that at least one collision happens. Return the answer modulo 10^9 + 7
.
Key Insights:
- Each monkey has exactly 2 choices: move clockwise or counterclockwise to a neighboring vertex
- Total possible ways for all monkeys to move =
2^n
(since each of the n monkeys has 2 choices) - The only ways to avoid ALL collisions are:
- All monkeys move clockwise together
- All monkeys move counterclockwise together
- These are the only 2 non-collision scenarios out of
2^n
total scenarios - Therefore, collision scenarios =
2^n - 2
The solution uses fast power to efficiently compute 2^n mod (10^9 + 7)
, then subtracts 2 to get the final answer.
Intuition
Let's think about when collisions DON'T happen first, as this is often easier to reason about.
Imagine all monkeys are standing on their vertices. If every monkey decides to move in the same direction (all clockwise or all counterclockwise), what happens? Each monkey moves to the position where their neighbor was standing. Since everyone moves in sync and in the same direction, no one meets on a vertex and no one crosses paths on an edge. It's like a perfect rotation of the entire configuration.
These are the ONLY two ways to avoid collisions completely. Why? Consider if even one monkey moves differently from the others:
- If monkey A moves clockwise while monkey B (its clockwise neighbor) moves counterclockwise, they'll meet on the edge between them - collision!
- If monkeys are not all moving in the same direction, there must be at least one pair of adjacent monkeys moving toward each other, causing a collision.
Now we can count:
- Total possible movements: Each of
n
monkeys has 2 choices (clockwise or counterclockwise), giving us2^n
total possibilities - Non-collision movements: Only 2 ways (all clockwise or all counterclockwise)
- Collision movements:
2^n - 2
This transforms a seemingly complex collision-counting problem into a simple subtraction: total ways minus non-collision ways. The use of modular arithmetic and fast power is just an implementation detail to handle large values of n
efficiently.
Solution Approach
The implementation uses fast power (modular exponentiation) to efficiently compute 2^n mod (10^9 + 7)
.
Here's how the solution works:
-
Define the modulus: We set
mod = 10^9 + 7
as required by the problem to prevent integer overflow for large values. -
Calculate 2^n using fast power: The built-in Python function
pow(2, n, mod)
computes2^n mod (10^9 + 7)
efficiently using the fast power algorithm. This works inO(log n)
time instead ofO(n)
time that naive multiplication would take. -
Subtract the non-collision cases: We subtract 2 from the result to exclude the two non-collision scenarios (all monkeys moving clockwise or all counterclockwise).
-
Apply modulus again: After subtraction, we apply
% mod
once more to ensure the result remains within the valid range. This is important because subtracting 2 from a number that's already been reduced by modulo might give a negative result if2^n mod (10^9 + 7)
is less than 2.
The complete formula is: (pow(2, n, mod) - 2) % mod
Why fast power?
- For large values of
n
, computing2^n
directly would cause integer overflow - Fast power with modular arithmetic keeps intermediate results manageable
- Time complexity:
O(log n)
instead ofO(n)
- Space complexity:
O(1)
The elegance of this solution lies in counting the complement (non-collision cases) rather than directly counting collision cases, reducing a complex combinatorial problem to a simple arithmetic operation.
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 n = 4
(a square with 4 monkeys at vertices 0, 1, 2, 3).
Step 1: Count total possible movements
- Each monkey has 2 choices: clockwise (CW) or counterclockwise (CCW)
- Total possibilities = 2^4 = 16 ways
Step 2: Identify non-collision scenarios
- Scenario 1: All monkeys move CW
- Monkey at 0 → 1, Monkey at 1 → 2, Monkey at 2 → 3, Monkey at 3 → 0
- Result: Monkeys end up at different vertices, no crossings
- Scenario 2: All monkeys move CCW
- Monkey at 0 → 3, Monkey at 1 → 0, Monkey at 2 → 1, Monkey at 3 → 2
- Result: Monkeys end up at different vertices, no crossings
- Total non-collision ways = 2
Step 3: Identify a collision example
- Let's say: Monkeys at 0,1,2 move CW, but monkey at 3 moves CCW
- Monkey at 2 moves to vertex 3 (CW)
- Monkey at 3 moves to vertex 2 (CCW)
- These two monkeys cross paths on edge 2-3 → Collision!
Step 4: Calculate collision scenarios
- Collision ways = Total ways - Non-collision ways
- Collision ways = 16 - 2 = 14
Step 5: Apply the formula with modular arithmetic
mod = 10^9 + 7 result = (pow(2, 4, mod) - 2) % mod result = (16 - 2) % mod result = 14
Therefore, there are 14 ways for the monkeys to move such that at least one collision occurs.
Solution Implementation
1class Solution:
2 def monkeyMove(self, n: int) -> int:
3 """
4 Calculate the number of ways monkeys can move in a circle.
5
6 The problem involves n monkeys arranged in a circle, where each monkey
7 can move clockwise or counterclockwise simultaneously. We need to find
8 the number of valid configurations where no collision occurs.
9
10 The total number of ways all monkeys can move is 2^n (each monkey has 2 choices).
11 However, we need to exclude the 2 cases where all monkeys move in the same
12 direction (all clockwise or all counterclockwise) as these don't result in
13 a valid rearrangement.
14
15 Args:
16 n: Number of monkeys in the circle
17
18 Returns:
19 Number of valid movement configurations modulo 10^9 + 7
20 """
21 # Define the modulo value to prevent integer overflow
22 MOD = 10**9 + 7
23
24 # Calculate 2^n mod MOD efficiently using built-in pow with modulo
25 # Then subtract 2 (the two invalid cases where all move same direction)
26 # Apply modulo again to ensure the result is non-negative
27 result = (pow(2, n, MOD) - 2) % MOD
28
29 return result
30
1class Solution {
2 /**
3 * Calculate the number of ways monkeys can move in a circle.
4 * The formula is (2^n - 2) % mod, where we subtract 2 to exclude
5 * the two cases where all monkeys move in the same direction (clockwise or counterclockwise).
6 *
7 * @param n The number of monkeys/positions in the circle
8 * @return The number of valid movement configurations modulo 10^9 + 7
9 */
10 public int monkeyMove(int n) {
11 final int MOD = (int) 1e9 + 7;
12
13 // Calculate 2^n mod MOD, then subtract 2 (avoiding negative results)
14 // Adding MOD before final modulo ensures positive result
15 return (quickPower(2, n, MOD) - 2 + MOD) % MOD;
16 }
17
18 /**
19 * Calculate (base^exponent) % modulo using binary exponentiation.
20 * This method efficiently computes large powers by repeatedly squaring.
21 *
22 * @param base The base number to be raised to a power
23 * @param exponent The power to raise the base to
24 * @param modulo The modulo value to prevent overflow
25 * @return (base^exponent) % modulo
26 */
27 private int quickPower(long base, int exponent, int modulo) {
28 long result = 1;
29
30 // Process each bit of the exponent from right to left
31 while (exponent > 0) {
32 // If the current bit is 1, multiply result by current base
33 if ((exponent & 1) == 1) {
34 result = (result * base) % modulo;
35 }
36
37 // Square the base for the next bit position
38 base = (base * base) % modulo;
39
40 // Shift exponent right by 1 bit (equivalent to dividing by 2)
41 exponent >>= 1;
42 }
43
44 return (int) result;
45 }
46}
47
1class Solution {
2public:
3 int monkeyMove(int n) {
4 // Define modulo constant for large number calculations
5 const int MOD = 1000000007;
6
7 // Type alias for long long to handle large integers
8 using ll = long long;
9
10 // Lambda function to calculate (base^exponent) % MOD using binary exponentiation
11 auto quickPower = [&](ll base, int exponent) {
12 ll result = 1;
13
14 // Binary exponentiation algorithm
15 while (exponent > 0) {
16 // If current bit is 1, multiply result by current base
17 if (exponent & 1) {
18 result = (result * base) % MOD;
19 }
20 // Square the base for next bit position
21 base = (base * base) % MOD;
22 // Right shift to process next bit
23 exponent >>= 1;
24 }
25
26 return result;
27 };
28
29 // Calculate total ways monkeys can move: 2^n - 2
30 // The -2 accounts for the two invalid cyclic patterns (all clockwise and all counterclockwise)
31 // Add MOD before final modulo to handle negative result
32 return (quickPower(2, n) - 2 + MOD) % MOD;
33 }
34};
35
1/**
2 * Calculates the number of ways monkeys can move in a circular arrangement
3 * @param n - The number of positions/steps
4 * @returns The number of valid movement configurations modulo 10^9 + 7
5 */
6function monkeyMove(n: number): number {
7 const MOD: number = 10 ** 9 + 7;
8
9 /**
10 * Performs fast modular exponentiation using binary exponentiation
11 * @param base - The base number to be raised to a power
12 * @param exponent - The power to raise the base to
13 * @returns (base^exponent) % MOD
14 */
15 const quickPower = (base: number, exponent: number): number => {
16 let result: bigint = 1n;
17
18 // Process exponent bit by bit from right to left
19 while (exponent > 0) {
20 // If current bit is 1, multiply result by current base
21 if (exponent & 1) {
22 result = (result * BigInt(base)) % BigInt(MOD);
23 }
24
25 // Square the base for next bit position
26 base = Number((BigInt(base) * BigInt(base)) % BigInt(MOD));
27
28 // Right shift exponent by 1 bit (equivalent to dividing by 2)
29 exponent >>>= 1;
30 }
31
32 return Number(result);
33 };
34
35 // Calculate 2^n - 2 (mod MOD)
36 // Adding MOD before final modulo ensures non-negative result
37 return (quickPower(2, n) - 2 + MOD) % MOD;
38}
39
Time and Space Complexity
Time Complexity: O(log n)
The time complexity is determined by the pow(2, n, mod)
function call. Python's built-in pow
function with three arguments uses fast exponentiation (also known as binary exponentiation or exponentiation by squaring) to compute 2^n mod (10^9 + 7)
. This algorithm works by repeatedly squaring and reducing the exponent by half, requiring approximately log₂(n)
multiplications and modulo operations. Each multiplication and modulo operation with the given modulus can be considered O(1)
for practical purposes since the numbers are bounded by the modulus value. Therefore, the overall time complexity is O(log n)
.
Space Complexity: O(1)
The space complexity is constant because the algorithm only uses a fixed amount of extra space regardless of the input size n
. The variables used are:
mod
: stores a single integer constant10^9 + 7
- The internal space used by
pow(2, n, mod)
for iterative fast exponentiation is alsoO(1)
- The final subtraction and modulo operation use constant space
No additional data structures that scale with input size are created, making the space complexity O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting the Final Modulo Operation
The Pitfall:
Many developers write pow(2, n, MOD) - 2
without the final % MOD
, assuming the result is already within bounds since pow(2, n, MOD)
returns a value less than MOD.
Why It's Wrong:
When n = 1
, we have:
2^1 mod (10^9 + 7) = 2
- Subtracting 2 gives us
2 - 2 = 0
✓
But when n = 2
:
2^2 mod (10^9 + 7) = 4
- Subtracting 2 gives us
4 - 2 = 2
✓
However, consider edge cases where the modular result could be 0 or 1:
- If somehow
2^n mod (10^9 + 7)
equals 0, then0 - 2 = -2
(negative!) - If somehow
2^n mod (10^9 + 7)
equals 1, then1 - 2 = -1
(negative!)
While for this specific problem with MOD = 10^9 + 7, 2^n mod MOD
will never be 0, 1, or 2 for valid n values, it's still best practice to apply the final modulo to handle negative results properly.
Correct Solution:
result = (pow(2, n, MOD) - 2) % MOD
2. Using Regular Power Instead of Modular Exponentiation
The Pitfall:
# WRONG - causes overflow for large n result = (2**n - 2) % MOD
Why It's Wrong:
2**n
computes the full value before applying modulo- For large n (e.g., n = 1000),
2^1000
is astronomically large and causes integer overflow in most languages - Even in Python (which handles big integers), this is inefficient
Correct Solution:
# RIGHT - uses modular exponentiation
result = (pow(2, n, MOD) - 2) % MOD
3. Implementing Custom Fast Power Incorrectly
The Pitfall: Some developers try to implement their own fast power function but make mistakes:
def fast_power(base, exp, mod):
result = 1
while exp > 0:
if exp % 2 == 1:
result = result * base % mod # Missing parentheses!
base = base * base % mod
exp //= 2
return result
Why It's Wrong:
The operator precedence issue: result * base % mod
is evaluated as result * (base % mod)
due to operator precedence, which can lead to overflow before the modulo is applied.
Correct Solution:
def fast_power(base, exp, mod):
result = 1
while exp > 0:
if exp % 2 == 1:
result = (result * base) % mod # Parentheses ensure correct order
base = (base * base) % mod
exp //= 2
return result
Or simply use Python's built-in pow(base, exp, mod)
which handles this correctly and efficiently.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
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
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!