1175. Prime Arrangements
Problem Description
You are given an integer n
representing numbers from 1 to n
. You need to find how many valid permutations exist where all prime numbers are placed at prime indices.
Key Points:
- You have numbers from 1 to
n
that need to be arranged - Indices are 1-indexed (positions start from 1, not 0)
- Prime numbers must be placed at prime positions
- Non-prime numbers must be placed at non-prime positions
- A prime number is an integer greater than 1 that cannot be written as a product of two positive integers both smaller than it
For example, if n = 5
:
- Numbers: 1, 2, 3, 4, 5
- Prime numbers: 2, 3, 5
- Prime indices: 2, 3, 5
- Non-prime numbers: 1, 4
- Non-prime indices: 1, 4
Valid arrangements would place {2, 3, 5} in positions {2, 3, 5} and {1, 4} in positions {1, 4}.
The answer should be returned modulo 10^9 + 7
since the result can be very large.
The solution involves:
- Counting how many prime numbers exist in the range
[1, n]
using the Sieve of Eratosthenes algorithm - Calculating the number of ways to arrange prime numbers in prime positions:
factorial(count_of_primes)
- Calculating the number of ways to arrange non-prime numbers in non-prime positions:
factorial(n - count_of_primes)
- Multiplying these two values together to get the total number of valid permutations
Intuition
The key insight is recognizing that this is a constrained permutation problem that can be broken down into two independent sub-problems.
First, let's understand what constraints we have:
- Prime numbers can ONLY go to prime positions
- Non-prime numbers can ONLY go to non-prime positions
This means we're essentially dealing with two separate groups that don't interact with each other. Once we realize this separation, the problem becomes much simpler.
Think of it like having two types of objects and two types of slots:
- Type A objects (prime numbers) can only go into Type A slots (prime indices)
- Type B objects (non-prime numbers) can only go into Type B slots (non-prime indices)
If we have cnt
prime numbers in the range [1, n]
, then we also have exactly cnt
prime positions (since prime positions are determined by the same prime numbers in the range [1, n]
).
Now, how many ways can we arrange cnt
prime numbers in cnt
prime positions? This is simply cnt!
(factorial of cnt).
Similarly, we have (n - cnt)
non-prime numbers and (n - cnt)
non-prime positions. The number of ways to arrange them is (n - cnt)!
.
Since these two arrangements are independent (choosing how to arrange primes doesn't affect how we can arrange non-primes), we multiply the possibilities: cnt! × (n - cnt)!
.
The challenge then reduces to efficiently counting how many primes exist in [1, n]
, which is where the Sieve of Eratosthenes comes in - it's an efficient algorithm to find all primes up to a given number by iteratively marking multiples of each prime as composite.
Learn more about Math patterns.
Solution Approach
The solution implements the mathematical approach using the Sieve of Eratosthenes to count primes, followed by factorial calculations.
Step 1: Count Prime Numbers Using Sieve of Eratosthenes
The count
function implements the sieve algorithm:
- Initialize a boolean array
primes
of size(n + 1)
where all values start asTrue
- Start iterating from
i = 2
ton
- If
primes[i]
isTrue
, theni
is prime:- Increment the prime counter
cnt
- Mark all multiples of
i
(starting from2i
) asFalse
since they're composite
- Increment the prime counter
- The inner loop
for j in range(i + i, n + 1, i)
marks2i, 3i, 4i, ...
as non-prime
This efficiently identifies all primes in O(n log log n)
time.
Step 2: Calculate Permutations
Once we have the count of primes (cnt
):
- Calculate
factorial(cnt)
- the number of ways to arrange prime numbers in prime positions - Calculate
factorial(n - cnt)
- the number of ways to arrange non-prime numbers in non-prime positions - Multiply these two values together:
ans = factorial(cnt) * factorial(n - cnt)
Step 3: Apply Modulo
Return the answer modulo 10^9 + 7
to handle large numbers: ans % (10**9 + 7)
Example Walkthrough:
For n = 5
:
- Sieve finds primes: 2, 3, 5 →
cnt = 3
- Non-primes count:
5 - 3 = 2
- Answer:
3! × 2! = 6 × 2 = 12
The algorithm efficiently combines number theory (prime detection) with combinatorics (permutation counting) to solve the problem in O(n log log n)
time for the sieve plus O(n)
for factorial calculations.
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 = 5
:
Step 1: Identify primes and positions
- Numbers we have: 1, 2, 3, 4, 5
- Using Sieve of Eratosthenes to find primes:
- Start with all numbers marked as potential primes
- 2 is prime → mark 4 as non-prime
- 3 is prime → mark 6, 9... (beyond our range)
- 4 is already marked non-prime
- 5 is prime
- Prime numbers: {2, 3, 5} → count = 3
- Non-prime numbers: {1, 4} → count = 2
- Prime positions (1-indexed): {2, 3, 5}
- Non-prime positions: {1, 4}
Step 2: Calculate arrangements
-
Prime numbers in prime positions:
- We have 3 primes for 3 prime positions
- Ways to arrange: 3! = 3 × 2 × 1 = 6
- Possible arrangements: (2,3,5), (2,5,3), (3,2,5), (3,5,2), (5,2,3), (5,3,2)
-
Non-prime numbers in non-prime positions:
- We have 2 non-primes for 2 non-prime positions
- Ways to arrange: 2! = 2 × 1 = 2
- Possible arrangements: (1,4) or (4,1)
Step 3: Combine the results
- Total permutations = 3! × 2! = 6 × 2 = 12
- Apply modulo: 12 % (10^9 + 7) = 12
Verification of one valid permutation:
- Position 1 (non-prime): 1 (non-prime) ✓
- Position 2 (prime): 2 (prime) ✓
- Position 3 (prime): 3 (prime) ✓
- Position 4 (non-prime): 4 (non-prime) ✓
- Position 5 (prime): 5 (prime) ✓
The algorithm correctly identifies that we can arrange the prime numbers among themselves in 6 ways and non-prime numbers among themselves in 2 ways, giving us 12 total valid permutations.
Solution Implementation
1class Solution:
2 def numPrimeArrangements(self, n: int) -> int:
3 def count_primes(n: int) -> int:
4 """
5 Count the number of prime numbers from 1 to n using Sieve of Eratosthenes.
6
7 Args:
8 n: Upper bound (inclusive) to check for primes
9
10 Returns:
11 Number of prime numbers from 1 to n
12 """
13 # Initialize count of primes
14 prime_count = 0
15
16 # Create a boolean array to track prime numbers (index represents the number)
17 is_prime = [True] * (n + 1)
18
19 # 0 and 1 are not prime numbers
20 if n >= 0:
21 is_prime[0] = False
22 if n >= 1:
23 is_prime[1] = False
24
25 # Sieve of Eratosthenes algorithm
26 for i in range(2, n + 1):
27 if is_prime[i]:
28 # Found a prime number
29 prime_count += 1
30
31 # Mark all multiples of i as non-prime
32 for j in range(i * 2, n + 1, i):
33 is_prime[j] = False
34
35 return prime_count
36
37 def factorial(num: int) -> int:
38 """
39 Calculate factorial of a number.
40
41 Args:
42 num: Non-negative integer
43
44 Returns:
45 Factorial of num
46 """
47 result = 1
48 for i in range(1, num + 1):
49 result *= i
50 return result
51
52 # Count how many prime numbers exist from 1 to n
53 prime_count = count_primes(n)
54
55 # Count of non-prime numbers
56 non_prime_count = n - prime_count
57
58 # Calculate total arrangements:
59 # - Primes can be arranged in prime_count! ways
60 # - Non-primes can be arranged in non_prime_count! ways
61 # - Total arrangements = prime_count! * non_prime_count!
62 total_arrangements = factorial(prime_count) * factorial(non_prime_count)
63
64 # Return result modulo 10^9 + 7 to prevent overflow
65 MOD = 10**9 + 7
66 return total_arrangements % MOD
67
1class Solution {
2 // Modulo constant for preventing integer overflow
3 private static final int MOD = (int) 1e9 + 7;
4
5 /**
6 * Calculate the number of prime arrangements for integers from 1 to n.
7 * Prime numbers must be placed at prime indices (1-indexed),
8 * and composite numbers must be placed at composite indices.
9 *
10 * @param n the upper bound of the range [1, n]
11 * @return the number of valid arrangements modulo 10^9 + 7
12 */
13 public int numPrimeArrangements(int n) {
14 // Count the number of prime numbers from 1 to n
15 int primeCount = countPrimes(n);
16
17 // Calculate the number of composite numbers
18 int compositeCount = n - primeCount;
19
20 // Total arrangements = (primeCount!) * (compositeCount!)
21 // Primes can be arranged in primeCount! ways
22 // Composites can be arranged in compositeCount! ways
23 long totalArrangements = calculateFactorial(primeCount) * calculateFactorial(compositeCount);
24
25 return (int) (totalArrangements % MOD);
26 }
27
28 /**
29 * Calculate factorial of a number with modulo operation.
30 *
31 * @param n the number to calculate factorial for
32 * @return n! % MOD
33 */
34 private long calculateFactorial(int n) {
35 long result = 1;
36
37 // Calculate n! = 1 * 2 * 3 * ... * n
38 for (int i = 2; i <= n; i++) {
39 result = (result * i) % MOD;
40 }
41
42 return result;
43 }
44
45 /**
46 * Count the number of prime numbers from 1 to n using Sieve of Eratosthenes.
47 *
48 * @param n the upper bound
49 * @return the count of prime numbers in range [1, n]
50 */
51 private int countPrimes(int n) {
52 int primeCount = 0;
53
54 // Initialize boolean array to track prime numbers
55 // Initially assume all numbers are prime
56 boolean[] isPrime = new boolean[n + 1];
57 Arrays.fill(isPrime, true);
58
59 // Sieve of Eratosthenes algorithm
60 for (int i = 2; i <= n; i++) {
61 if (isPrime[i]) {
62 // i is a prime number
63 primeCount++;
64
65 // Mark all multiples of i as non-prime
66 for (int j = i + i; j <= n; j += i) {
67 isPrime[j] = false;
68 }
69 }
70 }
71
72 return primeCount;
73 }
74}
75
1using ll = long long;
2const int MOD = 1e9 + 7;
3
4class Solution {
5public:
6 /**
7 * Calculate the number of prime arrangements for numbers from 1 to n
8 * Prime numbers must be placed at prime indices (1-indexed)
9 * Non-prime numbers must be placed at non-prime indices
10 * @param n: the upper bound of the range [1, n]
11 * @return: the number of valid arrangements modulo 10^9 + 7
12 */
13 int numPrimeArrangements(int n) {
14 // Count the number of primes in range [1, n]
15 int primeCount = countPrimes(n);
16
17 // Calculate the product of factorial of primes and factorial of non-primes
18 // primes can be arranged in primeCount! ways
19 // non-primes can be arranged in (n - primeCount)! ways
20 ll result = factorial(primeCount) * factorial(n - primeCount);
21
22 return static_cast<int>(result % MOD);
23 }
24
25private:
26 /**
27 * Calculate factorial of n modulo MOD
28 * @param n: the number to calculate factorial for
29 * @return: n! % MOD
30 */
31 ll factorial(int n) {
32 ll result = 1;
33 for (int i = 2; i <= n; ++i) {
34 result = (result * i) % MOD;
35 }
36 return result;
37 }
38
39 /**
40 * Count the number of prime numbers in range [2, n]
41 * Uses Sieve of Eratosthenes algorithm
42 * @param n: the upper bound of the range
43 * @return: count of prime numbers
44 */
45 int countPrimes(int n) {
46 // Initialize all numbers as potential primes
47 vector<bool> isPrime(n + 1, true);
48 int primeCount = 0;
49
50 // Sieve of Eratosthenes
51 for (int i = 2; i <= n; ++i) {
52 if (isPrime[i]) {
53 ++primeCount;
54 // Mark all multiples of i as non-prime
55 for (int j = i + i; j <= n; j += i) {
56 isPrime[j] = false;
57 }
58 }
59 }
60
61 return primeCount;
62 }
63};
64
1const MOD = 1e9 + 7;
2
3/**
4 * Calculate the number of prime arrangements for numbers from 1 to n
5 * Prime numbers must be placed at prime indices (1-indexed)
6 * Non-prime numbers must be placed at non-prime indices
7 * @param n - the upper bound of the range [1, n]
8 * @returns the number of valid arrangements modulo 10^9 + 7
9 */
10function numPrimeArrangements(n: number): number {
11 // Count the number of primes in range [1, n]
12 const primeCount = countPrimes(n);
13
14 // Calculate the product of factorial of primes and factorial of non-primes
15 // primes can be arranged in primeCount! ways
16 // non-primes can be arranged in (n - primeCount)! ways
17 const result = factorial(primeCount) * factorial(n - primeCount);
18
19 return Number(result % BigInt(MOD));
20}
21
22/**
23 * Calculate factorial of n modulo MOD
24 * @param n - the number to calculate factorial for
25 * @returns n! % MOD as a BigInt
26 */
27function factorial(n: number): bigint {
28 let result = BigInt(1);
29 for (let i = 2; i <= n; i++) {
30 result = (result * BigInt(i)) % BigInt(MOD);
31 }
32 return result;
33}
34
35/**
36 * Count the number of prime numbers in range [2, n]
37 * Uses Sieve of Eratosthenes algorithm
38 * @param n - the upper bound of the range
39 * @returns count of prime numbers
40 */
41function countPrimes(n: number): number {
42 // Initialize all numbers as potential primes
43 const isPrime: boolean[] = new Array(n + 1).fill(true);
44 let primeCount = 0;
45
46 // Sieve of Eratosthenes
47 for (let i = 2; i <= n; i++) {
48 if (isPrime[i]) {
49 primeCount++;
50 // Mark all multiples of i as non-prime
51 for (let j = i + i; j <= n; j += i) {
52 isPrime[j] = false;
53 }
54 }
55 }
56
57 return primeCount;
58}
59
Time and Space Complexity
Time Complexity: O(n × log log n)
The time complexity is dominated by the Sieve of Eratosthenes algorithm used in the count
function to find prime numbers up to n
.
- The outer loop runs from
2
ton
, iteratingO(n)
times - For each prime number
i
, the inner loop marks its multiples as non-prime, runningn/i
iterations - The total number of operations is approximately
n/2 + n/3 + n/5 + n/7 + ...
for all primes up ton
- This sum equals
n × (1/2 + 1/3 + 1/5 + 1/7 + ...)
where the series is the sum of reciprocals of primes - By mathematical analysis, this sum converges to
O(log log n)
- Therefore, the sieve algorithm has time complexity
O(n × log log n)
The factorial calculations take O(cnt)
and O(n - cnt)
time respectively, where cnt
is the number of primes. Since both are at most O(n)
, they don't affect the overall complexity.
Space Complexity: O(n)
The space complexity comes from:
- The
primes
boolean array of sizen + 1
, which requiresO(n)
space - The recursive call stack for factorial calculation (if implemented recursively) would be at most
O(n)
depth - Other variables use
O(1)
space
Therefore, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Factorial Calculation
The most critical pitfall in this solution is calculating large factorials without applying modulo during the computation. When n
is large, factorial(prime_count)
and factorial(non_prime_count)
can exceed Python's integer limits in other languages or cause performance issues even in Python.
Problem Code:
def factorial(num: int) -> int:
result = 1
for i in range(1, num + 1):
result *= i
return result
# Later...
total_arrangements = factorial(prime_count) * factorial(non_prime_count)
return total_arrangements % MOD
Solution: Apply modulo during each multiplication step to keep numbers manageable:
def factorial_mod(num: int, mod: int) -> int:
result = 1
for i in range(1, num + 1):
result = (result * i) % mod
return result
# In main function
MOD = 10**9 + 7
arrangements_primes = factorial_mod(prime_count, MOD)
arrangements_non_primes = factorial_mod(non_prime_count, MOD)
total_arrangements = (arrangements_primes * arrangements_non_primes) % MOD
return total_arrangements
2. Incorrect Sieve Implementation for Edge Cases
Another pitfall is mishandling the sieve when n
is 0 or 1, or forgetting that 1 is not a prime number.
Problem: Not properly initializing for small values of n
:
# This could cause index errors or incorrect counts is_prime = [True] * (n + 1) is_prime[0] = False # Could fail if n < 0 is_prime[1] = False # Could fail if n < 1
Solution: Handle edge cases explicitly:
def count_primes(n: int) -> int:
if n < 2:
return 0
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False
# Rest of sieve implementation...
3. Confusion Between 0-indexed and 1-indexed Positions
The problem states that positions are 1-indexed, but the sieve implementation uses 0-indexed arrays. This can lead to off-by-one errors when counting primes at prime positions.
Key Point: The number 2 is at position 2 (1-indexed), which is prime. The implementation correctly handles this because:
- The sieve counts how many prime numbers exist in range [1, n]
- The positions [1, n] have the same prime/non-prime distribution as numbers [1, n]
- Therefore, the count of prime numbers equals the count of prime positions
4. Inefficient Sieve Loop Structure
Problem: Starting the inner loop from i * i
instead of i * 2
:
# This might miss some composite markings for small numbers
for j in range(i * i, n + 1, i):
is_prime[j] = False
While mathematically correct (smaller multiples are already marked), using i * 2
is clearer and avoids potential confusion:
for j in range(i * 2, n + 1, i):
is_prime[j] = False
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
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
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!