Hello! In our last couple of lessons, we've built a solid foundation in prime numbers. We started by learning how to determine if a single number is prime in O(sqrt(n)) time, and then we scaled that up by using the Sieve of Eratosthenes to find all primes up to a limit N. Today, we'll address another fundamental task: breaking a number down into its core components.
This lesson will teach you how to find the prime factorization of any given integer n. We'll build directly on the logic from our primality test to develop an efficient O(sqrt(n)) algorithm. This is a cornerstone skill in competitive programming, appearing frequently as a building block for more complex problems.
The Fundamental Theorem of Arithmetic
At the heart of today's topic is a concept you may already know: the Fundamental Theorem of Arithmetic. It states that every integer greater than 1 is either a prime number itself or can be represented as a unique product of prime numbers.
For example:
60 = 2 * 2 * 3 * 599 = 3 * 3 * 117is just7(it's already prime)
Our goal is to write a program that takes an integer n and outputs this list of prime factors.

From a Naive Approach to an Efficient One
How might we start? A very basic idea would be to iterate a number i from 2 up to n. If i divides n, we've found a factor. Since we are checking in increasing order, the first factor we find must be prime. We can then repeatedly divide n by i to remove all occurrences of that factor.
This approach, however, can be very slow. Consider what happens if the input n is a large prime number. The loop would have to run all the way up to n, leading to an O(n) complexity, which is too slow for competitive programming constraints.
To understand this bottleneck and the need for optimization, the following video provides a clear explanation.
L03 : Prime factorization in O(sqrt(N)) | Number Theory | CodeNCode
This video from CodeNCode explains the O(N) brute-force approach and precisely why it fails for large prime inputs—a classic Time Limit Exceeded scenario.
Watch the section from the brute force approach to understand its logic and, more importantly, its worst-case performance.
The O(sqrt(n)) Optimization
Fortunately, we can do much better. The key insight is the same one we used for the O(sqrt(n)) primality test:
If a composite number
nhas a prime factor, it must have at least one prime factor less than or equal tosqrt(n).
This means we don't need to check for factors all the way up to n. We can stop our search at sqrt(n). This dramatically reduces our search space and is the core of the efficient algorithm.

But this raises a question: What about prime factors that are larger than sqrt(n)?
Let's think about it. An integer n can have at most one prime factor greater than sqrt(n). Why? If it had two such factors, say p1 and p2 where p1 > sqrt(n) and p2 > sqrt(n), then their product p1 * p2 would be greater than sqrt(n) * sqrt(n) = n. This is a contradiction.
So, our strategy is:
- Find and divide out all prime factors up to
sqrt(n). - After this process, if the remaining value of
nis greater than 1, it must be that single, large prime factor.
For a clear explanation of this logic, we can continue with the CodeNCode video.
L03 : Prime factorization in O(sqrt(N)) | Number Theory | CodeNCode
This part of the video introduces the sqrt(n) optimization and walks through an example, clarifying how the algorithm correctly handles the remaining prime factor.
Watch from the optimization claim where the sqrt(n) property is introduced. Then, continue through the example from the walkthrough, which shows how the algorithm handles a number composed of multiple primes.
The Algorithm in C++
Let's translate this logic into a clean C++ implementation. The cp-algorithms website, an essential resource for competitive programmers, provides a canonical implementation.
This article presents the standard trial division algorithm for prime factorization. We'll focus on the primary O(sqrt(n)) implementation.
Read the section titled Trial division. Focus on the logic explained in the paragraphs and then study the C++ code block for trial_division1. Pay close attention to how the for loop, while loop, and the final if (n > 1) statement work together. You can find this from the start of the section.
Let's break down that trial_division1 code:
vector<long long> trial_division1(long long n) {
vector<long long> factorization;
// 1. Loop for potential divisors up to sqrt(n)
for (long long d = 2; d * d <= n; d++) {
// 2. If d is a divisor, extract all powers of it
while (n % d == 0) {
factorization.push_back(d);
n /= d;
}
}
// 3. Handle the case of a large prime factor
if (n > 1)
factorization.push_back(n);
return factorization;
}
- The
forloop iterates through potential divisorsdfrom 2 up tosqrt(n). The conditiond * d <= nis a common and safe way to checkd <= sqrt(n)while avoiding floating-point arithmetic and potential precision issues. - The
whileloop is crucial. Ifddividesn, it addsdto the factors list and then dividesnbyd. It repeats this untilnis no longer divisible byd. This ensures we extract all occurrences of a prime factor (e.g., all three2s from24). - The final
if (n > 1)is the elegant part that handles the case we discussed earlier. Ifnhas been fully factored by primes less than or equal to its original square root, the remainingnwill be 1. If there was a prime factor greater than the originalsqrt(n)(e.g., the factor 13 inn=26), it will be the value ofnthat remains at the end.
A Small Optimization: Wheel Factorization
We can make a small but effective improvement. Once we check for the factor 2, we know that no other even number can be a prime factor. We can then start our loop at 3 and increment by 2, checking only odd numbers. This cuts the number of iterations in the main loop by half.
This idea is a simplified version of "Wheel Factorization". The GeeksforGeeks article below provides an implementation that shows this exact optimization.
This article also explains the O(sqrt(n)) method. Its implementation provides a practical optimization by handling the prime factor 2 separately.
Look at the C++ code under the heading Expected Approach. Notice how it has a while loop specifically for n % 2 == 0, and the subsequent for loop starts at i = 3 and increments by i = i + 2.
Putting It All Together: Sieve and Factorization
We can connect today's lesson with our previous one on the Sieve of Eratosthenes. If a problem requires you to factorize many numbers within a certain range (e.g., up to 10^6), you can gain a significant speedup.
- Pre-computation: Run the Sieve of Eratosthenes to generate all prime numbers up to
sqrt(N_max), whereN_maxis the maximum value ofnyou need to factor. - Factorization: In your factorization function, instead of iterating
d = 2, 3, 4, 5, ..., iterate only through the list of pre-computed primes.
This avoids checking composite numbers like 4, 6, 8, etc., as potential divisors, making your factorization loop even faster. The cp-algorithms article briefly describes this powerful combination.
This section explains how to combine the Sieve with trial division for even greater efficiency in certain scenarios.
Read the section Precomputed primes to see the code structure for this combined approach.
This technique is a perfect example of the pre-computation mindset that is essential for success in competitive programming.
Key Takeaways
- The prime factorization of a number is its unique representation as a product of primes.
- A naive
O(n)approach is too slow for large inputs. - The efficient
O(sqrt(n))algorithm relies on the fact that any composite numbernhas a prime factor less than or equal tosqrt(n). - The algorithm's key steps are:
- Iterate through potential divisors
dup tosqrt(n). - For each
d, use awhileloop to dividenbydrepeatedly until it's no longer divisible. - After the loop, if
nis still greater than 1, this remainingnis the last prime factor.
- Iterate through potential divisors
- This method can be further optimized by handling
2separately and only checking odd divisors, or by using a pre-computed list of primes from a sieve.
We have now covered primality testing, generating primes with a sieve, and prime factorization. These are the absolute fundamentals of number theory for competitive programming. In our next lesson, we will shift gears to explore the relationship between two numbers by learning the Euclidean algorithm to find their Greatest Common Divisor (GCD).