LeetCode 3896 - Minimum Operations to Transform Array into Alternating Prime
The problem asks us to transform an integer array nums into an alternating prime array, where numbers at even indices are prime and numbers at odd indices are non-prime.
Difficulty: 🟡 Medium
Topics: —
Solution
Problem Understanding
The problem asks us to transform an integer array nums into an alternating prime array, where numbers at even indices are prime and numbers at odd indices are non-prime. We are allowed to increment any number by 1 in one operation, and the goal is to minimize the total number of operations required.
The input is an array of integers with up to $10^5$ elements, and each element ranges from 1 to $10^5$. The expected output is a single integer representing the minimum number of increment operations needed.
Important points include:
- Prime numbers start from 2.
- Non-prime numbers include 1 and all composite numbers.
- Incrementing always moves a number closer to the next valid state (either prime or non-prime).
Edge cases that could trip a naive solution are small arrays, arrays where all numbers are already alternating, arrays with maximum values, and numbers at the boundaries of prime gaps (e.g., 2 to 3, 3 to 5).
Because the input size can be large ($10^5$), we need a solution that avoids repeated expensive prime checks for each number.
Approaches
The brute-force approach would check each element, increment it repeatedly until it satisfies the alternating prime condition. For even indices, we increment until the number becomes prime; for odd indices, we increment until the number becomes non-prime. While correct, this method can be very slow because checking primes repeatedly is expensive, and some numbers might need many increments.
The optimal approach relies on precomputing prime numbers using a sieve. With a prime lookup table, we can quickly find the next prime greater than or equal to a given number, allowing us to compute the number of increments in O(1) per element. Non-prime numbers can be detected similarly since we only need to check if a number is prime; if it is, we increment once to make it non-prime. This reduces the overall time complexity to O(N + P), where P is the sieve preprocessing (here P = $10^5$).
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(N * sqrt(M)) | O(1) | Check each element individually, increment until valid, M is max number |
| Optimal | O(N + M) | O(M) | Precompute primes using sieve, use lookup to compute increments efficiently |
Algorithm Walkthrough
- Precompute primes: Use the Sieve of Eratosthenes to compute all primes up to a slightly higher number than the maximum in
nums(to handle increments). Store this in a boolean arrayis_prime. - Compute next primes: For efficient increment calculation, precompute an array
next_prime[i]which stores the smallest prime ≥i. This allows us to calculate increments to reach a prime in O(1). - Initialize operation counter: Set a variable
operations = 0. - Iterate through the array: For each index
iinnums:
- If
iis even, calculateoperations += next_prime[nums[i]] - nums[i]. - If
iis odd, check ifnums[i]is prime. If it is, increment by 1 and add 1 tooperations; otherwise, add 0.
- Return total operations.
Why it works: Each element is independently transformed to satisfy the alternating prime condition with the minimal number of increments, and the precomputed next prime table guarantees we never overshoot or need repeated prime checks.
Python Solution
class Solution:
def minOperations(self, nums: list[int]) -> int:
MAX = 10**5 + 10
is_prime = [True] * MAX
is_prime[0] = is_prime[1] = False
# Sieve of Eratosthenes
for i in range(2, int(MAX**0.5) + 1):
if is_prime[i]:
for j in range(i*i, MAX, i):
is_prime[j] = False
# Precompute next prime for each number
next_prime = [0] * MAX
next_p = 0
for i in range(MAX-1, -1, -1):
if is_prime[i]:
next_p = i
next_prime[i] = next_p
operations = 0
for i, num in enumerate(nums):
if i % 2 == 0:
# even index, must be prime
operations += next_prime[num] - num
else:
# odd index, must be non-prime
if is_prime[num]:
operations += 1
return operations
This implementation first precomputes all primes and the next prime for each number. Then it iterates through the array, calculating the minimal increments based on the precomputed data. Using the sieve avoids repeated primality checks and ensures efficiency for large arrays.
Go Solution
func minOperations(nums []int) int {
const MAX = 100010
isPrime := make([]bool, MAX)
for i := 2; i < MAX; i++ {
isPrime[i] = true
}
// Sieve of Eratosthenes
for i := 2; i*i < MAX; i++ {
if isPrime[i] {
for j := i*i; j < MAX; j += i {
isPrime[j] = false
}
}
}
// Precompute next prime
nextPrime := make([]int, MAX)
nextP := 0
for i := MAX - 1; i >= 0; i-- {
if isPrime[i] {
nextP = i
}
nextPrime[i] = nextP
}
operations := 0
for i, num := range nums {
if i%2 == 0 {
operations += nextPrime[num] - num
} else {
if isPrime[num] {
operations++
}
}
}
return operations
}
The Go solution mirrors the Python logic with slice-based arrays and explicit initialization. We handle edge indices and prime checks carefully, ensuring correctness with the sieve and next prime array.
Worked Examples
Example 1: nums = [1,2,3,4]
| Index | Value | Target Type | Next Value / Increment | Operations |
|---|---|---|---|---|
| 0 | 1 | prime | 2 | 1 |
| 1 | 2 | non-prime | 4 | 2 |
| 2 | 3 | prime | 3 | 0 |
| 3 | 4 | non-prime | 4 | 0 |
Total operations = 3
Example 2: nums = [5,6,7,8]
| Index | Value | Target Type | Next Value / Increment | Operations |
|---|---|---|---|---|
| 0 | 5 | prime | 5 | 0 |
| 1 | 6 | non-prime | 6 | 0 |
| 2 | 7 | prime | 7 | 0 |
| 3 | 8 | non-prime | 8 | 0 |
Total operations = 0
Example 3: nums = [4,4]
| Index | Value | Target Type | Next Value / Increment | Operations |
|---|---|---|---|---|
| 0 | 4 | prime | 5 | 1 |
| 1 | 4 | non-prime | 4 | 0 |
Total operations = 1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(N + M) | Sieve preprocessing O(M), iterating nums O(N) |
| Space | O(M) | Arrays for is_prime and next_prime |
Here, N is the length of nums and M = 10^5 + 10 is the maximum number range. This ensures efficient handling of large arrays.
Test Cases
# Provided examples
assert Solution().minOperations([1,2,3,4]) == 3 # mixed primes and non-primes
assert Solution().minOperations([5,6,7,8]) == 0 # already alternating
assert Solution().minOperations([4,4]) == 1 # small array
# Boundary cases
assert Solution().minOperations([1]) == 1 # single element, must become prime
assert Solution().minOperations([2]) == 0 # single element, already prime
assert Solution().minOperations([1,1]) == 1 # both need adjustment: index 0 -> prime
# Stress cases
assert Solution().minOperations([10**5]*10**5) >= 0 # large values
| Test | Why |
|---|---|
| [1,2,3,4] | validates basic prime/non-prime transformation |
| [5,6,7,8] | tests already valid alternating array |
| [4,4] | checks minimal operations in a small array |
| [1] | single element prime requirement |
| [2] | single element already prime |
| [1,1] | both elements need adjustment |
| [10^5]*10^5 | stress test with maximum input size |
Edge Cases
Single element array: If nums