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:
- You can only use a previously unlocked computer
j
to decrypt computeri
's password - Computer
j
must have a smaller label than computeri
(meaningj < i
) - Computer
j
must have a lower complexity than computeri
(meaningcomplexity[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.
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
wherej < i
(smaller label) andcomplexity[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 anyi > 0
, we always have0 < 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
ton-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:
- Initialize
ans = 1
andmod = 10^9 + 7
- Iterate through each computer from index 1 to n-1
- If any computer has
complexity[i] ≤ complexity[0]
, return 0 immediately - Otherwise, multiply
ans
byi
(building up the factorial) - 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 EvaluatorExample 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:
-
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) ✓
-
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) ✓
-
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.
How many times is a tree node visited in a depth first search?
Recommended Readings
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
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
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!