Facebook Pixel

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:

  1. Counting how many prime numbers exist in the range [1, n] using the Sieve of Eratosthenes algorithm
  2. Calculating the number of ways to arrange prime numbers in prime positions: factorial(count_of_primes)
  3. Calculating the number of ways to arrange non-prime numbers in non-prime positions: factorial(n - count_of_primes)
  4. Multiplying these two values together to get the total number of valid permutations
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 as True
  • Start iterating from i = 2 to n
  • If primes[i] is True, then i is prime:
    • Increment the prime counter cnt
    • Mark all multiples of i (starting from 2i) as False since they're composite
  • The inner loop for j in range(i + i, n + 1, i) marks 2i, 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:

  1. Sieve finds primes: 2, 3, 5 → cnt = 3
  2. Non-primes count: 5 - 3 = 2
  3. 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 Evaluator

Example 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 to n, iterating O(n) times
  • For each prime number i, the inner loop marks its multiples as non-prime, running n/i iterations
  • The total number of operations is approximately n/2 + n/3 + n/5 + n/7 + ... for all primes up to n
  • 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 size n + 1, which requires O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

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

Load More