Facebook Pixel

3233. Find the Count of Numbers Which Are Not Special

MediumArrayMathNumber Theory
Leetcode Link

Problem Description

You are given two positive integers l and r. For any number x, all positive divisors of x except x are called the proper divisors of x.

A number is called special if it has exactly 2 proper divisors. For example:

  • The number 4 is special because it has proper divisors 1 and 2.
  • The number 6 is not special because it has proper divisors 1, 2, and 3.

Return the count of numbers in the range [l, r] that are not special.

Intuition

To solve the problem, we need to identify numbers that are not special within the given range [l, r].

A number is special if it has exactly two proper divisors. This occurs if and only if the number is a square of a prime number. This is because:

  • A prime number p has divisors 1 and p (itself), but its square p^2 has divisors 1, p, and p^2. Thus, p^2 has exactly two proper divisors (1 and p).

Considering the above, our goal is to count the special numbers in the given range. Once we have this count, the non-special numbers can be determined by subtracting the count of special numbers from the total numbers in range [l, r].

Solution Approach

  1. Preprocess Primes: First, we find all primes up to sqrt(10^9), which is roughly 31622. This preprocessing step helps us determine the squares of primes efficiently.

  2. Count Special Numbers: For each prime found in the preprocessing step, check if its square lies within the range [l, r]. If it does, it is a special number.

  3. Calculate Non-Special Numbers: Compute the total numbers in the range [l, r] as r - l + 1. Subtract the count of special numbers from this total to get the count of non-special numbers.

This approach efficiently leverages the properties of prime numbers and their squares to solve the problem within acceptable time limits.

Learn more about Math patterns.

Solution Approach

To solve the problem of identifying count of non-special numbers in the range [l, r], we implement a mathematical approach based on properties of numbers. Here's how it's done:

  1. Preprocessing Primes: The algorithm first involves generating primes up to √10^9 (approximately 31623). This is crucial because only the squares of these primes could be special numbers within the given range.

    • We use the Sieve of Eratosthenes, an efficient algorithm for finding all primes up to a specified integer n. This works by iteratively marking the multiples of each prime number starting from 2.
    • In the code, primes is an array that helps indicate whether a number is prime (True) or not (False), initialized up to 31623.
  2. Range Calculation:

    • For a given range [l, r], we need to determine the lower bound lo as ceil(sqrt(l)) and the upper bound hi as floor(sqrt(r)). These bounds represent the minimum and maximum possible squares of integers that need checking.
  3. Count Special Numbers:

    • We iterate through the range from lo to hi. For each integer in this range, check its primality. If it's prime, its square lies within the range [l, r], making it a special number.
    • In the code, we use the expression sum(primes[i] for i in range(lo, hi + 1)) to count primes within this range.
  4. Compute Non-Special Numbers:

    • Finally, the solution returns the count of non-special numbers, computed as r - l + 1 - cnt, where cnt is the number of special numbers found.

The solution efficiently identifies primes and calculates their squares, providing a direct way to count non-special numbers by leveraging mathematical properties of numbers and efficient computational techniques.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a simple example to understand the solution approach.

Suppose we have the range [l, r] where l = 10 and r = 20.

  1. Preprocessing Primes:

    • We first find all primes up to √20 ≈ 4.47, which means we consider numbers up to 4. The primes in this range are 2 and 3.
  2. Range Calculation:

    • Calculate the lower bound lo = ceil(√10) = 4 and the upper bound hi = floor(√20) = 4. Thus, lo and hi both equal 4, which indicates potential candidates for special numbers are numbers whose square is 4.
  3. Count Special Numbers:

    • We check if 4 is generated by squaring a prime within the calculated bounds. Since 2^2 = 4, 4 is a special number because 2 is prime.
    • Special numbers in the range are 4 (from the prime number 2).
  4. Compute Non-Special Numbers:

    • The total numbers in the range [l, r] are 20 - 10 + 1 = 11.
    • There is 1 special number (4).
    • Therefore, the count of non-special numbers is 11 - 1 = 10.

Thus, in the given range [10, 20], there are 10 non-special numbers.

Solution Implementation

1from math import ceil, floor, sqrt
2
3# Maximum value for computing primes
4max_value = 31623
5
6# A list to determine if numbers up to max_value are prime
7is_prime = [True] * (max_value + 1)
8# 0 and 1 are not prime numbers
9is_prime[0] = is_prime[1] = False
10
11# Sieve of Eratosthenes to mark non-prime numbers
12for i in range(2, max_value + 1):
13    if is_prime[i]:
14        # Mark all multiples of i as non-prime
15        for j in range(i + i, max_value + 1, i):
16            is_prime[j] = False
17
18class Solution:
19    def nonSpecialCount(self, lower: int, upper: int) -> int:
20        # Calculate the smallest integer greater than or equal to the square root of lower
21        lower_bound = ceil(sqrt(lower))
22        # Calculate the largest integer less than or equal to the square root of upper
23        upper_bound = floor(sqrt(upper))
24        # Count the number of prime numbers between lower_bound and upper_bound (inclusive)
25        prime_count = sum(is_prime[i] for i in range(lower_bound, upper_bound + 1))
26        # Return the total numbers in range minus the count of "special numbers" (primes within the calculated bounds)
27        return upper - lower + 1 - prime_count
28
1import java.util.Arrays;
2
3class Solution {
4
5    // Define an upper limit for checking prime numbers
6    static final int LIMIT = 31623;
7    // Array to mark prime numbers
8    static boolean[] isPrimeArray = new boolean[LIMIT + 1];
9
10    // Static initializer block to populate the prime array
11    static {
12        // Initialize all numbers as prime (true)
13        Arrays.fill(isPrimeArray, true);
14        // 0 and 1 are not prime numbers
15        isPrimeArray[0] = isPrimeArray[1] = false;
16      
17        // Implement the Sieve of Eratosthenes algorithm to find all prime numbers up to LIMIT
18        for (int i = 2; i <= LIMIT; i++) {
19            if (isPrimeArray[i]) {
20                // Mark all multiples of i as non-prime
21                for (int j = i + i; j <= LIMIT; j += i) {
22                    isPrimeArray[j] = false;
23                }
24            }
25        }
26    }
27
28    // Method to count non-special numbers between a given range [l, r]
29    public int nonSpecialCount(int l, int r) {
30        // Calculate the ceiling of square root of l as the starting point
31        int lowerBound = (int) Math.ceil(Math.sqrt(l));
32        // Calculate the floor of square root of r as the ending point
33        int upperBound = (int) Math.floor(Math.sqrt(r));
34        int countOfSpecialNumbers = 0;
35
36        // Iterate over the range from lowerBound to upperBound
37        for (int i = lowerBound; i <= upperBound; i++) {
38            // If i is a prime number
39            if (isPrimeArray[i]) {
40                countOfSpecialNumbers++;
41            }
42        }
43
44        // Calculate and return the number of non-special numbers in the range [l, r]
45        return r - l + 1 - countOfSpecialNumbers;
46    }
47}
48
1#include <cmath>
2#include <cstring>
3
4const int MAX_LIMIT = 31623;
5bool isPrime[MAX_LIMIT + 1];
6
7// Initialize the prime numbers array using the Sieve of Eratosthenes
8auto init = [] {
9    memset(isPrime, true, sizeof(isPrime)); // Set all entries to true initially
10    isPrime[0] = isPrime[1] = false; // 0 and 1 are not prime numbers
11  
12    // Perform Sieve of Eratosthenes to mark non-prime numbers
13    for (int i = 2; i <= MAX_LIMIT; ++i) {
14        if (isPrime[i]) {
15            for (int j = i * 2; j <= MAX_LIMIT; j += i) {
16                isPrime[j] = false; // Mark multiples of i as not prime
17            }
18        }
19    }
20    return 0;
21}();
22
23class Solution {
24public:
25    int nonSpecialCount(int left, int right) {
26        int start = std::ceil(std::sqrt(left));  // Calculate the lower boundary
27        int end = std::floor(std::sqrt(right)); // Calculate the upper boundary
28        int primeCount = 0; // Initialize prime count
29      
30        // Count primes within the range from start to end
31        for (int i = start; i <= end; ++i) {
32            if (isPrime[i]) {
33                ++primeCount; // Increment count if i is prime
34            }
35        }
36      
37        // Calculate and return the count of non-special numbers
38        return right - left + 1 - primeCount;
39    }
40};
41
1// Maximum possible square root value for the given range limit
2const MAX_PRIME = 31623;
3
4// Array to track prime numbers; initialized with true
5const primes: boolean[] = Array(MAX_PRIME + 1).fill(true);
6
7// Immediately Invoked Function Expression (IIFE) to populate the primes array using the Sieve of Eratosthenes
8(() => {
9    // 0 and 1 are not prime numbers
10    primes[0] = primes[1] = false;
11
12    // Iterate over each number up to MAX_PRIME
13    for (let i = 2; i <= MAX_PRIME; ++i) {
14        if (primes[i]) { // If the number is still considered prime
15            // Mark all multiples of i starting from i*i as non-prime
16            for (let j = i * 2; j <= MAX_PRIME; j += i) {
17                primes[j] = false;
18            }
19        }
20    }
21})();
22
23/**
24 * Function to count numbers in the range [l, r] that are not perfect squares of prime numbers
25 * @param l - The lower bound of the range
26 * @param r - The upper bound of the range
27 * @returns The count of non-special numbers
28 */
29function nonSpecialCount(l: number, r: number): number {
30    // Calculate the smallest integer greater than or equal to the square root of l
31    const lowerBound = Math.ceil(Math.sqrt(l));
32  
33    // Calculate the largest integer less than or equal to the square root of r
34    const upperBound = Math.floor(Math.sqrt(r));
35  
36    let primeSquareCount = 0; // Counter for prime square numbers within the bounds
37
38    // Check each number from lowerBound to upperBound
39    for (let i = lowerBound; i <= upperBound; ++i) {
40        if (primes[i]) { // If i is a prime number
41            ++primeSquareCount;
42        }
43    }
44
45    // Total numbers in range minus the count of special numbers (prime squares)
46    return r - l + 1 - primeSquareCount;
47}
48

Time and Space Complexity

The given code primarily consists of two parts: the sieve of Eratosthenes to determine prime numbers up to m = 31623, and the calculation in the nonSpecialCount method.

Sieve of Eratosthenes

  1. Time Complexity: This part iteratively marks non-primes for each prime number i. The time complexity for the sieve is O(m log(log(m))) which simplifies practically to O(m), given m = 31623, as it processes up to each number and removes its multiples.

  2. Space Complexity: Utilizing a boolean list primes of size m + 1, the space complexity is O(m).

nonSpecialCount Function

  1. Time Complexity:
    • We compute lo = ceil(sqrt(l)) and hi = floor(sqrt(r)). Calculating the square roots takes O(1).
    • We sum prime numbers between lo and hi, which are at most O(sqrt(r) - sqrt(l)). In the worst scenario, this is limited by the range of m, which takes at most O(sqrt(m)) since hi is bounded by the initial sieve's upper bound.
  2. Space Complexity:
    • The space used is constant outside of the space taken by input parameters, resulting in O(1).
    • However, the primes array influences this as beforehand preparation, keeping the complexity O(m) overall for storage.

In context with m = 31623 and the constructed sieve, the reference answer stating O(\sqrt{m}) for both time and space pertains mainly to the tailored calculation of the prime counts within the specified range, relying on the precomputed list of primes.

Learn more about how to find time and space complexity quickly.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

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


Load More