Facebook Pixel

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.

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

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 us 2^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.

Learn more about Recursion and Math patterns.

Solution Approach

The implementation uses fast power (modular exponentiation) to efficiently compute 2^n mod (10^9 + 7).

Here's how the solution works:

  1. Define the modulus: We set mod = 10^9 + 7 as required by the problem to prevent integer overflow for large values.

  2. Calculate 2^n using fast power: The built-in Python function pow(2, n, mod) computes 2^n mod (10^9 + 7) efficiently using the fast power algorithm. This works in O(log n) time instead of O(n) time that naive multiplication would take.

  3. 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).

  4. 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 if 2^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, computing 2^n directly would cause integer overflow
  • Fast power with modular arithmetic keeps intermediate results manageable
  • Time complexity: O(log n) instead of O(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 Evaluator

Example 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 constant 10^9 + 7
  • The internal space used by pow(2, n, mod) for iterative fast exponentiation is also O(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, then 0 - 2 = -2 (negative!)
  • If somehow 2^n mod (10^9 + 7) equals 1, then 1 - 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More