3233. Find the Count of Numbers Which Are Not Special
Problem Description
You are given two positive integers l
and r
that define a range [l, r]
.
For any number x
, the proper divisors of x
are all positive divisors of x
except x
itself. For example:
- The proper divisors of 4 are 1 and 2 (excluding 4 itself)
- The proper divisors of 6 are 1, 2, and 3 (excluding 6 itself)
A number is called special if it has exactly 2 proper divisors.
Examples:
- The number 4 is special because it has exactly 2 proper divisors: 1 and 2
- The number 6 is not special because it has 3 proper divisors: 1, 2, and 3
Your task is to count how many numbers in the range [l, r]
are not special.
The key observation is that only perfect squares of prime numbers are special. This is because if p
is prime, then p²
has exactly three divisors: 1, p
, and p²
. Since proper divisors exclude p²
itself, we get exactly 2 proper divisors (1 and p
). All other numbers either have fewer than 2 or more than 2 proper divisors.
The solution uses the Sieve of Eratosthenes to precompute all prime numbers up to √(10⁹)
(since the maximum value of r
is 10⁹
). Then it counts how many primes exist in the range [⌈√l⌉, ⌊√r⌋]
, as the squares of these primes would be the special numbers in [l, r]
. The final answer is the total count of numbers in [l, r]
minus the count of special numbers.
Intuition
Let's think about what makes a number have exactly 2 proper divisors.
First, consider the divisor structure of different types of numbers:
- Prime numbers like 3 have divisors: 1, 3. So proper divisors: just 1 (only 1 proper divisor)
- Composite numbers like 6 have divisors: 1, 2, 3, 6. So proper divisors: 1, 2, 3 (3 proper divisors)
- Perfect squares like 9 have divisors: 1, 3, 9. So proper divisors: 1, 3 (2 proper divisors)
This pattern reveals something interesting: numbers with exactly 2 proper divisors seem to have exactly 3 total divisors.
What numbers have exactly 3 divisors? Let's think about the prime factorization. If a number n = p₁^a₁ × p₂^a₂ × ... × pₖ^aₖ
, then the number of divisors is (a₁ + 1) × (a₂ + 1) × ... × (aₖ + 1)
.
For this product to equal 3, and since 3 is prime, we need exactly one term equal to 3 and all others equal to 1. This means we need one exponent to be 2 and all others to be 0. In other words, n = p²
for some prime p
.
So special numbers are exactly the squares of primes! For example:
4 = 2²
has divisors 1, 2, 4 → proper divisors: 1, 2 ✓9 = 3²
has divisors 1, 3, 9 → proper divisors: 1, 3 ✓25 = 5²
has divisors 1, 5, 25 → proper divisors: 1, 5 ✓
Now, to count non-special numbers in [l, r]
, we need to count how many squares of primes fall in this range. If p²
is in [l, r]
, then √l ≤ p ≤ √r
. So we need to count primes in the range [⌈√l⌉, ⌊√r⌋]
.
This leads us to precompute all primes up to √(10⁹) ≈ 31623
using the Sieve of Eratosthenes, then count how many of these primes squared would fall in our range.
Learn more about Math patterns.
Solution Approach
The implementation consists of two main parts: preprocessing prime numbers and counting special numbers in the given range.
Step 1: Precompute Prime Numbers Using Sieve of Eratosthenes
We first calculate all prime numbers up to m = 31623
(which is approximately √(10⁹)
). This upper bound is chosen because the maximum value of r
can be 10⁹
, and we need to check primes whose squares could be at most 10⁹
.
m = 31623
primes = [True] * (m + 1)
primes[0] = primes[1] = False
for i in range(2, m + 1):
if primes[i]:
for j in range(i + i, m + 1, i):
primes[j] = False
The sieve works by:
- Initially marking all numbers as potentially prime (
True
) - Setting 0 and 1 as non-prime (
False
) - For each prime number
i
, marking all its multiples (i+i, i+2i, i+3i, ...
) as non-prime
Step 2: Count Special Numbers in Range [l, r]
For the given range [l, r]
, we need to find how many squares of primes fall within it.
def nonSpecialCount(self, l: int, r: int) -> int:
lo = ceil(sqrt(l))
hi = floor(sqrt(r))
cnt = sum(primes[i] for i in range(lo, hi + 1))
return r - l + 1 - cnt
The logic here:
- Calculate
lo = ⌈√l⌉
andhi = ⌊√r⌋
to find the range of potential primes - If
p
is a prime in[lo, hi]
, thenp²
is a special number in[l, r]
- Count all primes in the range
[lo, hi]
using our precomputed sieve - The total count of numbers in
[l, r]
isr - l + 1
- Subtract the count of special numbers to get the count of non-special numbers
Example Walkthrough:
For l = 5, r = 30
:
lo = ⌈√5⌉ = 3
andhi = ⌊√30⌋ = 5
- Check primes in range
[3, 5]
: 3 and 5 are prime - Special numbers:
3² = 9
and5² = 25
- Total numbers in
[5, 30]
:30 - 5 + 1 = 26
- Non-special numbers:
26 - 2 = 24
The time complexity is O(m log log m)
for the sieve preprocessing and O(√r - √l)
for counting, where m = 31623
.
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 small example with l = 8
and r = 20
.
Step 1: Identify what we're looking for
We need to count how many numbers in [8, 20]
are NOT special. Remember, a number is special if it has exactly 2 proper divisors, which only happens for squares of prime numbers.
Step 2: Find the range of potential primes
- Calculate
lo = ⌈√8⌉ = ⌈2.83...⌉ = 3
- Calculate
hi = ⌊√20⌋ = ⌊4.47...⌋ = 4
- So we need to check primes in the range
[3, 4]
Step 3: Identify primes in [3, 4]
Using our precomputed sieve:
- Is 3 prime? Yes ✓
- Is 4 prime? No (4 = 2 × 2)
So we have exactly 1 prime (which is 3) in our range.
Step 4: Find special numbers The square of our prime gives us the special number:
3² = 9
(which is in range[8, 20]
✓)
Let's verify that 9 is indeed special:
- Divisors of 9: {1, 3, 9}
- Proper divisors of 9: {1, 3} (excluding 9 itself)
- Count of proper divisors: 2 ✓
Step 5: Count non-special numbers
- Total numbers in
[8, 20]
:20 - 8 + 1 = 13
- Special numbers found: 1 (which is 9)
- Non-special numbers:
13 - 1 = 12
Verification:
The numbers in [8, 20]
are: 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
- Only 9 is special (square of prime 3)
- The other 12 numbers are not special
Therefore, the answer is 12.
Solution Implementation
1from math import ceil, floor, sqrt
2
3# Pre-compute all prime numbers up to 31623 using Sieve of Eratosthenes
4# 31623 is approximately sqrt(10^9), suitable for checking if sqrt of numbers up to 10^9 is prime
5MAX_LIMIT = 31623
6is_prime = [True] * (MAX_LIMIT + 1)
7is_prime[0] = is_prime[1] = False # 0 and 1 are not prime
8
9# Sieve of Eratosthenes algorithm
10for i in range(2, MAX_LIMIT + 1):
11 if is_prime[i]:
12 # Mark all multiples of i as non-prime
13 for j in range(i * 2, MAX_LIMIT + 1, i):
14 is_prime[j] = False
15
16
17class Solution:
18 def nonSpecialCount(self, l: int, r: int) -> int:
19 """
20 Count non-special numbers in the range [l, r].
21 A special number is a perfect square whose square root is prime.
22
23 Args:
24 l: Left boundary of the range (inclusive)
25 r: Right boundary of the range (inclusive)
26
27 Returns:
28 Count of non-special numbers in the range
29 """
30 # Find the range of potential square roots
31 # We need to check if numbers from l to r are perfect squares of primes
32 lower_bound = ceil(sqrt(l))
33 upper_bound = floor(sqrt(r))
34
35 # Count how many primes have their squares in the range [l, r]
36 special_count = sum(is_prime[i] for i in range(lower_bound, upper_bound + 1))
37
38 # Total numbers minus special numbers equals non-special numbers
39 total_numbers = r - l + 1
40 return total_numbers - special_count
41
1class Solution {
2 // Upper bound for prime numbers (sqrt of 10^9 is approximately 31623)
3 private static final int MAX_PRIME = 31623;
4
5 // Boolean array to mark prime numbers using Sieve of Eratosthenes
6 private static boolean[] isPrime = new boolean[MAX_PRIME + 1];
7
8 // Static initialization block to precompute all prime numbers up to MAX_PRIME
9 static {
10 // Initially mark all numbers as prime
11 Arrays.fill(isPrime, true);
12
13 // 0 and 1 are not prime numbers
14 isPrime[0] = false;
15 isPrime[1] = false;
16
17 // Sieve of Eratosthenes algorithm
18 for (int i = 2; i <= MAX_PRIME; i++) {
19 if (isPrime[i]) {
20 // Mark all multiples of i as non-prime
21 for (int j = i + i; j <= MAX_PRIME; j += i) {
22 isPrime[j] = false;
23 }
24 }
25 }
26 }
27
28 /**
29 * Counts the number of non-special integers in the range [l, r].
30 * A special number is the square of a prime number.
31 *
32 * @param l the lower bound of the range (inclusive)
33 * @param r the upper bound of the range (inclusive)
34 * @return the count of non-special numbers in the range
35 */
36 public int nonSpecialCount(int l, int r) {
37 // Find the smallest integer whose square is >= l
38 int lowerBound = (int) Math.ceil(Math.sqrt(l));
39
40 // Find the largest integer whose square is <= r
41 int upperBound = (int) Math.floor(Math.sqrt(r));
42
43 // Count how many primes exist in the range [lowerBound, upperBound]
44 // These primes, when squared, give us special numbers in [l, r]
45 int specialCount = 0;
46 for (int i = lowerBound; i <= upperBound; i++) {
47 if (isPrime[i]) {
48 specialCount++;
49 }
50 }
51
52 // Total numbers in range minus special numbers gives non-special count
53 int totalNumbers = r - l + 1;
54 return totalNumbers - specialCount;
55 }
56}
57
1// Global constant for the upper bound of prime sieve
2const int MAX_PRIME = 31623; // sqrt(10^9) ≈ 31623, sufficient for checking primes up to sqrt(r)
3
4// Global array to mark prime numbers using Sieve of Eratosthenes
5bool isPrime[MAX_PRIME + 1];
6
7// Lambda function for one-time initialization of the prime sieve
8// This runs automatically before main() due to the assignment to 'init'
9auto init = [] {
10 // Initialize all numbers as potentially prime
11 memset(isPrime, true, sizeof(isPrime));
12
13 // 0 and 1 are not prime numbers
14 isPrime[0] = isPrime[1] = false;
15
16 // Sieve of Eratosthenes algorithm
17 for (int i = 2; i <= MAX_PRIME; ++i) {
18 if (isPrime[i]) {
19 // Mark all multiples of i as non-prime
20 for (int j = i * 2; j <= MAX_PRIME; j += i) {
21 isPrime[j] = false;
22 }
23 }
24 }
25 return 0;
26}();
27
28class Solution {
29public:
30 /**
31 * Counts non-special numbers in the range [l, r]
32 * A special number is the square of a prime number
33 *
34 * @param l: left boundary of the range (inclusive)
35 * @param r: right boundary of the range (inclusive)
36 * @return: count of non-special numbers in [l, r]
37 */
38 int nonSpecialCount(int l, int r) {
39 // Find the range of numbers whose squares could be in [l, r]
40 int lowerBound = ceil(sqrt(l)); // Smallest integer whose square >= l
41 int upperBound = floor(sqrt(r)); // Largest integer whose square <= r
42
43 // Count how many primes exist in [lowerBound, upperBound]
44 // Their squares will be special numbers in [l, r]
45 int specialCount = 0;
46 for (int i = lowerBound; i <= upperBound; ++i) {
47 if (isPrime[i]) {
48 ++specialCount;
49 }
50 }
51
52 // Total numbers in range minus special numbers gives non-special count
53 int totalNumbers = r - l + 1;
54 return totalNumbers - specialCount;
55 }
56};
57
1// Maximum value for prime sieve (approximately sqrt(10^9))
2const MAX_PRIME_LIMIT = 31623;
3
4// Boolean array to track prime numbers using Sieve of Eratosthenes
5const isPrime: boolean[] = Array(MAX_PRIME_LIMIT + 1).fill(true);
6
7// Initialize the prime sieve using an IIFE (Immediately Invoked Function Expression)
8(() => {
9 // 0 and 1 are not prime numbers
10 isPrime[0] = isPrime[1] = false;
11
12 // Sieve of Eratosthenes algorithm
13 for (let i = 2; i <= MAX_PRIME_LIMIT; ++i) {
14 if (isPrime[i]) {
15 // Mark all multiples of i as non-prime
16 for (let j = i * 2; j <= MAX_PRIME_LIMIT; j += i) {
17 isPrime[j] = false;
18 }
19 }
20 }
21})();
22
23/**
24 * Counts the number of non-special integers in the range [l, r]
25 * A special number is a perfect square whose square root is a prime number
26 *
27 * @param l - The lower bound of the range (inclusive)
28 * @param r - The upper bound of the range (inclusive)
29 * @returns The count of non-special numbers in the range
30 */
31function nonSpecialCount(l: number, r: number): number {
32 // Calculate the range of potential square roots
33 const lowerBound = Math.ceil(Math.sqrt(l));
34 const upperBound = Math.floor(Math.sqrt(r));
35
36 // Count how many primes exist in the square root range
37 let specialCount = 0;
38 for (let i = lowerBound; i <= upperBound; ++i) {
39 if (isPrime[i]) {
40 // i^2 is a special number since i is prime
41 ++specialCount;
42 }
43 }
44
45 // Total numbers in range minus special numbers equals non-special numbers
46 return r - l + 1 - specialCount;
47}
48
Time and Space Complexity
The code consists of two parts: preprocessing (Sieve of Eratosthenes) and the main solution.
Preprocessing (Sieve of Eratosthenes):
- Time Complexity:
O(m log log m)
wherem = 31623
. This is because for each primei
, we mark its multiples as non-prime, and the sum ofm/i
for all primesi ≤ m
is approximatelym log log m
. - Space Complexity:
O(m)
for theprimes
array of sizem + 1
.
Main Solution (nonSpecialCount
method):
- Time Complexity:
O(√r - √l)
which is at mostO(√r)
. Sincer
can be up to10^9
, this isO(√10^9) = O(31623) = O(√m)
wherem = 10^9
. - Space Complexity:
O(1)
for the method itself (only using a few variables).
Overall Complexity:
- Time Complexity: The preprocessing runs once with
O(m log log m)
wherem = 31623 ≈ √10^9
, and each query runs inO(√r - √l)
. Since31623 = √10^9
, we can express this asO(√m log log √m) = O(√m log log m)
wherem = 10^9
. However, for practical purposes and considering thatlog log √m
is a very small constant, this simplifies toO(√m)
wherem = 10^9
. - Space Complexity:
O(31623) = O(√m)
wherem = 10^9
, for storing the prime sieve.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Boundary Calculation for Square Roots
One of the most common mistakes is incorrectly calculating the range of potential primes whose squares fall within [l, r]
.
Pitfall Example:
# WRONG: Using simple division or incorrect rounding
lower_bound = int(l ** 0.5) # May miss edge cases
upper_bound = int(r ** 0.5) # May miss valid primes
Why it's wrong:
- For
l = 5
, usingint(5 ** 0.5) = 2
would check if 2² = 4 is in range, but 4 < 5 - For
r = 30
, usingint(30 ** 0.5) = 5
works, but forr = 25
,int(25 ** 0.5) = 5
and 5² = 25 should be included
Correct Solution:
from math import ceil, floor, sqrt lower_bound = ceil(sqrt(l)) # Ensures p² >= l upper_bound = floor(sqrt(r)) # Ensures p² <= r
2. Off-by-One Errors in Range Iteration
Pitfall Example:
# WRONG: Forgetting to include upper_bound
special_count = sum(is_prime[i] for i in range(lower_bound, upper_bound))
Correct Solution:
# Include upper_bound by adding 1
special_count = sum(is_prime[i] for i in range(lower_bound, upper_bound + 1))
3. Insufficient Sieve Size
Pitfall Example:
# WRONG: Using a smaller sieve size MAX_LIMIT = 1000 # Too small for r up to 10^9
Why it's wrong: If r = 10^9
, we need to check primes up to √(10^9) ≈ 31623. A smaller sieve would miss valid primes.
Correct Solution:
MAX_LIMIT = 31623 # Covers sqrt(10^9)
4. Floating Point Precision Issues
Pitfall Example:
# WRONG: Direct floating point comparison
if sqrt(n) == int(sqrt(n)): # May have precision errors
# Check if it's a perfect square
Better Approach: The solution avoids this by working with the range of square roots directly rather than checking individual numbers for being perfect squares.
5. Global Variable Scope Issues
Pitfall Example:
class Solution:
def nonSpecialCount(self, l: int, r: int) -> int:
# WRONG: Computing sieve inside the method for each call
MAX_LIMIT = 31623
is_prime = [True] * (MAX_LIMIT + 1)
# ... sieve computation ...
Why it's wrong: This recomputes the sieve for every function call, which is inefficient for multiple test cases.
Correct Solution: Precompute the sieve once outside the class as a global variable, making it available for all test cases without recomputation.
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
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!