Prime Factors
Overview
A prime factor is a factor of a number that is also a prime number. For example, the prime factors of are and , as . Prime factorization is the process of finding the prime factors of a number. This can be done by repeatedly dividing the number by prime numbers until the remaining number is also a prime number. The prime factorization of a number is unique, meaning that there is only one combination of prime numbers that multiply together to equal that number. The prime factorization of a number can be useful in solving mathematical problems such as finding the greatest common divisor (GCD) of two numbers or the least common multiple (LCM) of two numbers.
Approach #1: Finding prime factor in time
We know that every composite number has a prime factor less than . Thus, we can iterate over numbers from to and whenever we find number that divides we divide by it until it no longer divides .
But wouldn't we accidently divide N by a non-prime number this way?
No, we won't because every composite number has a prime factor less than so as we iterate over to we will cancel out all prime factors of any non-prime numbers from making it indivisible by that non-prime number.
We can further speed up our algorithm by using the fact that a number can have atmost prime factor greater than . This can be easily proved by assuming that a number contains two prime factors greater than , let's call them and .
Since and . This is not possible as and are prime factor of so their product must be less than or equal to .
Implementation
- C++
vector<int> getPrimeFactors(int n) {
vector<int> prime_factors;
// i * i <= n or i <= sqrt(n) both work.
for (int i = 2; i * i <= n; i++) {
while (n % i == 0) {
prime_factors.emplace_back(i);
n /= i;
}
}
if (n > 1) prime_factors.emplace_back(n);
return prime_factors;
}
- Time Complexity:
Approach #2: Finding prime factor in log(n) time
Before moving further, it's important to recognise that a number cannot have more than prime factors. Since the smallest prime factor a number can have is and . If the number has a prime factor bigger than than it will have even lesser prime factors. The pre-requisite for this method is knowing Sieve of Eratosthenes.
We can optimise our code to find prime factors of a number in time. But to do so, we need to do some pre-computations which takes time, where is the largest number upto which we can find it's prime factors. Thus the final time complexity is . This is helpful only if we have to find prime factor of multiple numbers less than or equal to .
Since the pre computation takes time so we cannot find prime factors of very large numbers (due to constraints on time) using this method. The pre computation process is a modification of Sieve of Eratosthenes. For each number from to we find it's smallest prime factor and store it in an array, let's call it min_prime
. We will see how to find min_prime
array later, first let us see how to calculate prime factor of a number if we already have the min_prime
array in hand.
Implementation
- C++
// For sake of simplicity let us assume min_prime is a global vector.
vector<int> getPrimeFactorsInLogn(int n) {
vector<int> prime_factors;
while (n > 1) {
int smallest_prime_factor_of_n = min_prime[n];
prime_factors.push_back(smallest_prime_factor_of_n);
n /= smallest_prime_factor_of_n;
}
return prime_factors;
}
- Time Complexity: , since each time the number is divided by a number greater than equal to , so it will take only operation for to reach .
How to find min_prime array?
vector<int> minPrime(int MAX) {
vector<int> min_prime(MAX + 1);
// Set min_prime[i] = i
iota(begin(min_prime), end(min_prime), 0);
for (int i = 2; i * i <= MAX; i++) {
if (min_prime[i] != i) continue;
// if min_prime[i] = i then i must be a prime number.
// Any multiple of i less than i * i (i.e., i * 2, i * 3, ... i * (i - 1)) has a smaller prime factor than i.
// Any number >= i * i may have i as it's minimum prime factor
for (int j = i * i; j <= MAX; j += i) min_prime[j] = min(min_prime[j], i);
}
return min_prime;
}
Now, that we are aware the techniques used to find prime factors let us solve some problem to get a better grip over the topic.
Example #1: 2521. Distinct Prime Factors Product of Array
Let us take two numbers and , where is a prime numbers and .
What is ?
We know . Using this property we can say that Thus, we can find prime factor of individual numbers and just add up the power of primes. Function getPrimeFactors(n)
is same as coded in the start. To get distinct elements we use set
to store all prime factors of all numbers.
- C++
int distinctPrimeFactors(vector<int>& nums) {
set<int> distinct_pf;
for (auto num : nums) {
auto pf = getPrimeFactors(num);
for (auto prime : pf) distinct_pf.emplace(prime);
}
return distinct_pf.size();
}
- Time Complexity: , where is the max number present in
nums
and is length ofnums
. - Space Complexity: O(\text\{number of prime from 2 to MAX}).
We can code same approach using getPrimeFactorsInLogn()
function (Method-2).
- C++
class Solution {
vector<int> minPrime(int MAX) {
vector<int> min_prime(MAX + 1);
iota(begin(min_prime), end(min_prime), 0);
for (int i = 2; i * i <= MAX; i++) {
if (min_prime[i] != i) continue;
for (int j = i * i; j <= MAX; j += i) min_prime[j] = min(min_prime[j], i);
}
return min_prime;
}
// We need to pass the min_prime vector to use inside the function.
vector<int> getPrimeFactorsInLogn(int n, vector<int> &min_prime) {
vector<int> prime_factors;
while (n > 1) {
int smallest_prime_factor_of_n = min_prime[n];
prime_factors.push_back(smallest_prime_factor_of_n);
n /= smallest_prime_factor_of_n;
}
return prime_factors;
}
public:
int distinctPrimeFactors(vector<int>& nums) {
set<int> distinct_pf;
// Finding the limit upto which we should run sieve.
const int max = *max_element(begin(nums), end(nums));
auto min_prime = minPrime(max + 1);
for (auto num : nums) {
// Get prime factors of each number in log(n) time.
auto pf = getPrimeFactorsInLogn(num, min_prime);
for (auto prime : pf) distinct_pf.emplace(prime);
}
return distinct_pf.size();
}
};
- Time Complexity: , where is the max element of
nums
and is the length ofnums
. - Space Complexity: , since we need the
min_prime
vector. Other vector are of size only.
Example #2: 2507. Smallest Value After Replacing with Sum of Prime Factors
In this problem, we have keep replacing with sum of it's prime factors. We need to stop the process once becomes prime as a prime number has only two factors and itself. Since is not a prime number so it doesn't change. Once our process stops, we will have the minimum number, because sum of prime factor of any number is always less than itself.
Sum of prime factor of any number is always less than or equal to itself. Why?
If we take a composite number then it can be written as product of atleast different numbers. Let be a composite number such that , where .
We know that
Adding and we get,
- C++
class Solution {
public:
vector<int> getPrimeFactors(int n) {
vector<int> prime_factors;
// i * i <= n or i <= sqrt(n) both work.
for (int i = 2; i * i <= n; i++) {
while (n % i == 0) {
prime_factors.emplace_back(i);
n /= i;
}
}
if (n > 1) prime_factors.emplace_back(n);
return prime_factors;
}
int smallestValue(int n) {
while (true) {
auto pf = getPrimeFactors(n);
int sum = accumulate(begin(pf), end(pf), 0);
if (sum == n) break;
n = sum;
}
return n;
}
};
- Time Complexity: .
- Space Complexity: , since there cannot be more than prime factor of a number .
The above code is bound to come out of while
loop in atmost steps. In the worst case, where is a prime number. In this case decrease from to which is approximately . Thus at each step decrease by a factor of . If it has bigger prime factors then it will take even less steps to terminate the while
loop.
Example #3: 952. Largest Component Size by Common Factor
This problem requires basic knowledge of graph traversal. The two popular traversals are Depth First Search and Breadth First Search.
First let us see how to build the graph. Naive way of building graph would be to iterate over all pairs and check if . If it is then add an edge bweten them, else not. But this approch will take operations in worst case, which will not pass.
A different way to build graph could be to add edge between and if is a prime factor of . Since any number has atmost prime factors so we will have atmost edges in the graph. But while counting component we need to make sure that the node is present in nums
. If it isn't then it can serve as a bridge connecting two numbers from nums
, but it should not included in count of nodes in connected component.
An Example
For Example: nums = [6, 8]
. In this case which is a prime factor of and acts as a bridge connecting to but total connected component should be only.
Graph for above example looks as shown below. Although total node in the connected component is but we do not count and when reporting answer as it isn't present in nums
. Image generated using CS Academy Graph Editor.
Now, as the graph is built we need three main functions:
getPrimeFactorsInLogn(n)
: Gives us prime factor of a number . Since is only so we prefer this function over the one which does the same thing in time.getComponentCount(src)
: Starting from node how many nodes (that are present innums
) can be reached from it.largestComponentSize(nums)
: The main function that builds graph and use other functions.
- C++
class Solution {
// isPresent[i] = 1 if i is present in nums, else 0
// g = graph using which we can find maximum connected component size
vector<int> isPresent;
vector<vector<int>> g;
vector<int> minPrime(int MAX) {
vector<int> min_prime(MAX + 1);
iota(begin(min_prime), end(min_prime), 0);
for (int i = 2; i * i <= MAX; i++) {
if (min_prime[i] != i) continue;
for (int j = i * i; j <= MAX; j += i) min_prime[j] = min(min_prime[j], i);
}
return min_prime;
}
vector<int> getPrimeFactorsInLogn(int n, vector<int> &min_prime) {
vector<int> prime_factors;
while (n > 1) {
int smallest_prime_factor_of_n = min_prime[n];
// Since we only need unique prime factors so we won't push same number again and again
if (n % smallest_prime_factor_of_n == 0) prime_factors.push_back(smallest_prime_factor_of_n);
// Remove the prime factor completely from n
while (n % smallest_prime_factor_of_n == 0) n /= smallest_prime_factor_of_n;
}
return prime_factors;
}
int getComponentSize(int src, vector<bool> &vis) {
queue<int> q;
q.emplace(src);
vis[src] = true;
int count = isPresent[src];
while (!q.empty()) {
int node = q.front();
q.pop();
for (int child : g[node]) {
if (!vis[child]) {
q.emplace(child);
vis[child] = true;
// Increase component size only if the number is present in nums.
count += isPresent[child];
}
}
}
return count;
}
public:
int largestComponentSize(vector<int>& nums) {
int max_elem = *max_element(begin(nums), end(nums));
auto min_prime = minPrime(max_elem + 1);
isPresent.resize(max_elem + 1);
g.resize(max_elem + 1);
vector<bool> vis(max_elem + 1);
for (int num : nums) {
isPresent[num] = 1;
auto pf = getPrimeFactorsInLogn(num, min_prime);
for (auto prime : pf) {
if (prime == num) continue;
// num and prime has gcd > 1
g[prime].emplace_back(num);
g[num].emplace_back(prime);
}
}
int ans = isPresent[1];
for (int src = 2; src <= max_elem; src++) {
ans = max(ans, getComponentSize(src, vis));
}
return ans;
}
};
- Time Complexity: , where is the max element of
nums
and is length ofnums
. - Space Complexity: , as we are building a graph of edges and nodes.
The process of finding connected components can be done using a data structure Disjoint Set Union as well.
Suggested Problems
Problem Name | Difficulty | Solution Link |
---|---|---|
263. Ugly Number | Easy | View Solutions |
650. 2 Keys Keyboard | Medium | View Solutions |
2709. Greatest Common Divisor Traversal | Hard | View Solutions |