Facebook Pixel

3577. Count the Number of Computer Unlocking Permutations

Problem Description

You have n locked computers in a room, labeled from 0 to n - 1. Each computer has a unique password with a specific complexity value given in the array complexity, where complexity[i] represents the complexity of computer i's password.

Computer 0 is special - its password is already decrypted and unlocked from the start. To unlock any other computer i, you need to follow these rules:

  1. You can only use a previously unlocked computer j to decrypt computer i's password
  2. Computer j must have a smaller label than computer i (meaning j < i)
  3. Computer j must have a lower complexity than computer i (meaning complexity[j] < complexity[i])

Your task is to find how many different orders (permutations) exist for unlocking all computers, starting with only computer 0 unlocked. The answer should be returned modulo 10^9 + 7.

Key insight: The problem asks for permutations of the sequence [0, 1, 2, ..., n-1] that represent valid unlocking orders. Note that computer 0 always starts unlocked regardless of its position in the permutation.

Why the solution works: Since computer 0 is the only initially unlocked computer, any other computer i can only be unlocked if complexity[i] > complexity[0]. If any computer has complexity less than or equal to computer 0's complexity, it becomes impossible to unlock that computer, making the answer 0. When all computers have complexity greater than computer 0's complexity, they can be unlocked in any order (as long as we respect the label ordering constraint), giving us (n-1)! valid permutations.

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

Intuition

Let's think about what constraints we actually have when unlocking computers. Computer 0 starts unlocked, and to unlock any computer i, we need a previously unlocked computer j where j < i and complexity[j] < complexity[i].

The first critical observation: Since computer 0 is our only starting point, every other computer must have complexity[i] > complexity[0] to be unlockable. Why? Because to unlock computer i (where i > 0), we need some unlocked computer with lower complexity. If complexity[i] ≤ complexity[0], we can never find such a computer since:

  • Computer 0 doesn't satisfy the complexity requirement
  • Any other computer j that could help would itself need to be unlocked first, but it faces the same problem

Once we verify all computers can be unlocked (all have complexity greater than computer 0), let's think about the actual unlocking order. Here's the key insight: the label ordering constraint j < i is always satisfied in any valid permutation!

Why? Consider any permutation of [0, 1, 2, ..., n-1]. When we try to unlock computer i in this sequence:

  • We can use any previously unlocked computer j where j < i (smaller label) and complexity[j] < complexity[i]
  • Since all computers have complexity > complexity[0], and computer 0 is always available, we can always find at least computer 0 as a valid option
  • The smaller label requirement j < i is automatically satisfied because for any i > 0, we always have 0 < i, and computer 0 is always unlocked

This means any ordering of the computers 1, 2, ..., n-1 works! We can unlock them in any sequence we want, giving us (n-1)! valid permutations.

Learn more about Math and Combinatorics patterns.

Solution Approach

The implementation is surprisingly simple once we understand the key insight from the problem analysis.

Step 1: Check if all computers can be unlocked

First, we need to verify that every computer i (where i > 0) has complexity[i] > complexity[0]. If any computer has complexity less than or equal to complexity[0], it's impossible to unlock that computer, so we return 0.

for i in range(1, len(complexity)):
    if complexity[i] <= complexity[0]:
        return 0

Step 2: Calculate the number of valid permutations

If all computers can be unlocked, then any ordering of computers 1, 2, ..., n-1 forms a valid unlocking sequence. This gives us (n-1)! possible permutations.

We calculate (n-1)! iteratively:

  • Start with ans = 1
  • Multiply by each value from 1 to n-1
  • Apply modulo 10^9 + 7 at each step to prevent overflow
mod = 10**9 + 7
ans = 1
for i in range(1, len(complexity)):
    ans = ans * i % mod

Complete Algorithm:

  1. Initialize ans = 1 and mod = 10^9 + 7
  2. Iterate through each computer from index 1 to n-1
  3. If any computer has complexity[i] ≤ complexity[0], return 0 immediately
  4. Otherwise, multiply ans by i (building up the factorial)
  5. Return the final answer

The time complexity is O(n) for the single pass through the array, and space complexity is O(1) as we only use a constant amount of extra space.

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 a concrete example with complexity = [2, 5, 3, 4].

Step 1: Check if all computers can be unlocked

We have 4 computers (labeled 0, 1, 2, 3). Computer 0 starts unlocked with complexity 2.

Let's check each computer:

  • Computer 1: complexity[1] = 5 > complexity[0] = 2 ✓ (can be unlocked)
  • Computer 2: complexity[2] = 3 > complexity[0] = 2 ✓ (can be unlocked)
  • Computer 3: complexity[3] = 4 > complexity[0] = 2 ✓ (can be unlocked)

All computers have complexity greater than computer 0, so they can all be unlocked.

Step 2: Understanding valid unlock sequences

Since computer 0 is always unlocked first, we need to determine valid orders for unlocking computers 1, 2, and 3.

Let's verify a few possible sequences:

  1. Sequence: [1, 2, 3]

    • Unlock computer 1: Use computer 0 (0 < 1, complexity[0]=2 < complexity[1]=5) ✓
    • Unlock computer 2: Use computer 0 (0 < 2, complexity[0]=2 < complexity[2]=3) ✓
    • Unlock computer 3: Use computer 0 (0 < 3, complexity[0]=2 < complexity[3]=4) ✓
  2. Sequence: [3, 1, 2]

    • Unlock computer 3: Use computer 0 (0 < 3, complexity[0]=2 < complexity[3]=4) ✓
    • Unlock computer 1: Use computer 0 (0 < 1, complexity[0]=2 < complexity[1]=5) ✓
    • Unlock computer 2: Use computer 0 (0 < 2, complexity[0]=2 < complexity[2]=3) ✓
  3. Sequence: [2, 3, 1]

    • Unlock computer 2: Use computer 0 (0 < 2, complexity[0]=2 < complexity[2]=3) ✓
    • Unlock computer 3: Use computer 2 (2 < 3, complexity[2]=3 < complexity[3]=4) ✓
    • Unlock computer 1: Use computer 0 (0 < 1, complexity[0]=2 < complexity[1]=5) ✓

Notice that in each sequence, we can always find a valid previously unlocked computer to use. Computer 0 acts as a universal unlocker since it has the lowest complexity and smallest label.

Step 3: Count the permutations

We have 3 computers (1, 2, 3) that need to be unlocked after computer 0. The number of ways to arrange these 3 computers is 3! = 3 × 2 × 1 = 6.

All 6 permutations are valid:

  • [1, 2, 3]
  • [1, 3, 2]
  • [2, 1, 3]
  • [2, 3, 1]
  • [3, 1, 2]
  • [3, 2, 1]

Step 4: Calculate using the algorithm

ans = 1
mod = 10^9 + 7

For i = 1: ans = 1 × 1 = 1
For i = 2: ans = 1 × 2 = 2
For i = 3: ans = 2 × 3 = 6

Return: 6

The answer is 6, representing the 6 different valid orders to unlock all computers.

Solution Implementation

1class Solution:
2    def countPermutations(self, complexity: List[int]) -> int:
3        # Define modulo for large number handling
4        MOD = 10**9 + 7
5      
6        # Check if all elements after the first are strictly greater than the first element
7        # This ensures the first element is the minimum in the array
8        for i in range(1, len(complexity)):
9            if complexity[i] <= complexity[0]:
10                # If any element is not greater than the first, no valid permutations exist
11                return 0
12      
13        # Calculate (n-1)! modulo MOD
14        # This represents the number of ways to arrange the remaining n-1 elements
15        result = 1
16        for i in range(1, len(complexity)):
17            result = (result * i) % MOD
18      
19        return result
20
1class Solution {
2    public int countPermutations(int[] complexity) {
3        // Define modulo constant for preventing integer overflow
4        final int MOD = 1_000_000_007;
5      
6        // Initialize result to 1 (representing one valid permutation initially)
7        long result = 1;
8      
9        // Iterate through all elements starting from index 1
10        for (int i = 1; i < complexity.length; i++) {
11            // Check if current element is less than or equal to the first element
12            // If so, no valid permutations exist (first element must be the smallest)
13            if (complexity[i] <= complexity[0]) {
14                return 0;
15            }
16          
17            // Multiply result by current index (calculating factorial-like product)
18            // Apply modulo to prevent overflow
19            result = (result * i) % MOD;
20        }
21      
22        // Cast long result back to int and return
23        return (int) result;
24    }
25}
26
1class Solution {
2public:
3    int countPermutations(vector<int>& complexity) {
4        // Define modulo constant for preventing integer overflow
5        const int MOD = 1000000007;
6      
7        // Initialize result variable to store the count of valid permutations
8        long long result = 1;
9      
10        // Iterate through the array starting from index 1
11        for (int i = 1; i < complexity.size(); ++i) {
12            // Check if current element is less than or equal to the first element
13            // If so, no valid permutation exists where first element is minimum
14            if (complexity[i] <= complexity[0]) {
15                return 0;
16            }
17          
18            // Multiply result by current index (counting valid positions)
19            // Apply modulo to prevent overflow
20            result = (result * i) % MOD;
21        }
22      
23        // Return the final count of permutations
24        return static_cast<int>(result);
25    }
26};
27
1/**
2 * Counts the number of valid permutations based on complexity constraints.
3 * Returns 0 if any element after the first is less than or equal to the first element.
4 * Otherwise, returns the factorial of (n-1) modulo 10^9+7.
5 * 
6 * @param complexity - Array of complexity values to check
7 * @returns Number of valid permutations modulo 10^9+7, or 0 if constraints are violated
8 */
9function countPermutations(complexity: number[]): number {
10    // Define modulo constant for large number arithmetic
11    const MOD: number = 1e9 + 7;
12  
13    // Initialize result accumulator
14    let result: number = 1;
15  
16    // Iterate through array starting from second element
17    for (let i: number = 1; i < complexity.length; i++) {
18        // Check if current element violates the constraint
19        // (must be greater than the first element)
20        if (complexity[i] <= complexity[0]) {
21            return 0;
22        }
23      
24        // Multiply result by current index and apply modulo
25        // This calculates (n-1)! incrementally
26        result = (result * i) % MOD;
27    }
28  
29    return result;
30}
31

Time and Space Complexity

The time complexity is O(n), where n is the length of the complexity array. The algorithm iterates through the array once from index 1 to len(complexity) - 1, performing constant-time operations (comparison, multiplication, and modulo) in each iteration.

The space complexity is O(1). The algorithm only uses a fixed amount of extra space for variables mod, ans, and the loop variable i, regardless of the input size. No additional data structures that grow with the input are created.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding the Problem Constraints

Pitfall: Many developers initially misinterpret the problem and think they need to check if complexity[j] < complexity[i] for all pairs where j < i. This leads to unnecessary O(n²) nested loops:

# INCORRECT approach - overcomplicated
def countPermutations(self, complexity: List[int]) -> int:
    MOD = 10**9 + 7
    n = len(complexity)
  
    # Wrong: Checking all pairs unnecessarily
    for i in range(1, n):
        can_unlock = False
        for j in range(i):
            if complexity[j] < complexity[i]:
                can_unlock = True
                break
        if not can_unlock:
            return 0
  
    # Calculate factorial...

Solution: Recognize that since computer 0 is the only initially unlocked computer, we only need to check if each computer can be unlocked by computer 0. This simplifies to checking if complexity[i] > complexity[0] for all i > 0.

2. Integer Overflow Issues

Pitfall: Forgetting to apply modulo operation during factorial calculation, causing integer overflow for large values:

# INCORRECT - will overflow for large n
def countPermutations(self, complexity: List[int]) -> int:
    MOD = 10**9 + 7
  
    # Check conditions...
  
    result = 1
    for i in range(1, len(complexity)):
        result = result * i  # Missing modulo operation
  
    return result % MOD  # Applying modulo only at the end

Solution: Apply the modulo operation at each multiplication step to keep numbers within bounds:

result = 1
for i in range(1, len(complexity)):
    result = (result * i) % MOD  # Apply modulo at each step

3. Edge Case Handling

Pitfall: Not properly handling edge cases like arrays with only one element:

# INCORRECT - doesn't handle n=1 case properly
def countPermutations(self, complexity: List[int]) -> int:
    if len(complexity) == 0:  # Wrong edge case check
        return 0
  
    # Rest of the code...

Solution: The correct implementation naturally handles the edge case where n=1. When there's only one computer (computer 0), the loop doesn't execute, and the function correctly returns 1 (representing the single valid "permutation" of doing nothing since computer 0 is already unlocked).

4. Using <= Instead of < in Comparison

Pitfall: Using the wrong comparison operator when checking complexities:

# INCORRECT - using wrong comparison
for i in range(1, len(complexity)):
    if complexity[i] < complexity[0]:  # Should be <=
        return 0

Solution: Use the correct comparison complexity[i] <= complexity[0] to return 0. The problem requires strict inequality (complexity[j] < complexity[i]) for unlocking, so if complexity[i] <= complexity[0], computer i cannot be unlocked.

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

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More