Print Perfect Numbers in Java
Problem
A perfect number is a number that equals the sum of its own proper divisors — printing every one in a range means checking that condition for every candidate.
Given an upper limit, print every perfect number up to and including it.
Java Program
public class PrintPerfectNumbers {
public static void main(String[] args) {
int limit = 500;
for (int num = 2; num <= limit; num++) {
int sum = 0;
for (int i = 1; i < num; i++) {
if (num % i == 0) sum += i; // add every proper divisor of this candidate
}
if (sum == num) {
System.out.println(num); // candidate matches its own divisor sum
}
}
}
}Output
Core Logic
Checking every candidate number in the range for the perfect-number condition, one divisor-sum scan at a time, finds every match by brute force.
- 1The outer loop tries every candidate
numfrom2up tolimit. - 2For each candidate, an inner loop sums every number from
1up tonum - 1that dividesnumevenly. - 3If that divisor sum equals
num, the candidate is a perfect number and gets printed. - 4The scan continues through the whole range, since perfect numbers are rare and spread far apart.
6, 28, and 496 have a divisor sum that equals themselves — every other candidate in between fails the check.Key Point: Perfect numbers get rare fast — after 6, 28, and 496, the next one is 8128, and the one after that is over 33 million, so even a full scan up to 500 only turns up three matches.
Why: Each candidate number gets its own O(candidate) divisor-sum scan, so the nested loops add up to roughly O(limit²) across the whole range.
Key Concepts
Approach 2: Optimized (Divisor Pairs)
public class PrintPerfectNumbersOptimized {
public static void main(String[] args) {
int limit = 500;
for (int num = 2; num <= limit; num++) {
int sum = 1; // 1 is a proper divisor of every number greater than 1
for (int i = 2; (long) i * i <= num; i++) {
if (num % i == 0) {
sum += i;
int pair = num / i;
if (pair != i) sum += pair; // add the matching divisor pair, unless it's the same value
}
}
if (sum == num) {
System.out.println(num); // candidate matches its own divisor sum
}
}
}
}
Output
Core Logic
Applying the same divisor-pairs shortcut used to check a single perfect number to every candidate in the range cuts each inner scan down to the square root.
- 1The outer loop is unchanged — it still tries every candidate
numfrom2tolimit. - 2The inner divisor-sum scan now only tries candidates up to
√num, adding both a divisor and its pair,num / i, whenever one is found. - 3The
pair != icheck still guards against double-counting a perfect square's middle divisor. - 4The same
sum == numcomparison decides whether to print the candidate.
28 within the range, the optimized inner scan only tries candidates up to 5 (since 6 * 6 > 28), instead of all the way up to 27.Key Point: This is the same trade-off as checking a single perfect number — the inner scan drops from O(candidate) to O(√candidate), which adds up across the whole range.
Why: Each candidate's divisor-sum check now only costs O(√candidate) instead of O(candidate), cutting the total work from roughly O(limit²) down to O(limit × √limit).
Key Concepts
Approach 3: Java 8
import java.util.stream.IntStream;
public class PrintPerfectNumbersStream {
public static void main(String[] args) {
int limit = 500;
// Keeps only the candidates whose proper-divisor sum equals themselves
IntStream.rangeClosed(2, limit)
.filter(num -> IntStream.range(1, num).filter(i -> num % i == 0).sum() == num)
.forEach(System.out::println);
}
}
Output
Core Logic
The same brute-force scan can be expressed as nested streams — filter the range down to only the candidates whose divisor sum matches themselves.
- 1
IntStream.rangeClosed(2, limit)generates every candidate in the range. - 2
.filter(num -> ...)keeps only the candidates that pass the perfect-number condition. - 3Inside that filter, a nested
IntStream.range(1, num).filter(i -> num % i == 0).sum()computes each candidate's divisor sum, mirroring the primary approach's inner loop. - 4
.forEach(System.out::println)prints every surviving candidate.
6, 28, and 496, printed in order.Key Point: This does the same O(limit²) work as the primary approach, just expressed as nested streams instead of nested loops — the divisor-pairs optimization still applies separately if performance matters.
Why: Each candidate still gets its own O(candidate) divisor-sum stream, so the total cost across the whole range is still roughly O(limit²), just expressed as nested streams instead of nested loops.