LeetCode 507 - Perfect Number
This problem asks us to determine whether a given positive integer num is a perfect number. A perfect number is defined as a number whose sum of positive divisors, excluding the number itself, equals the number.
Difficulty: 🟢 Easy
Topics: Math
Solution
Problem Understanding
This problem asks us to determine whether a given positive integer num is a perfect number.
A perfect number is defined as a number whose sum of positive divisors, excluding the number itself, equals the number. In other words, we must find all integers that divide num evenly, excluding num, sum them up, and check whether that sum equals the original number.
For example, consider 28:
1divides282divides284divides287divides2814divides28
The sum of these divisors is:
1 + 2 + 4 + 7 + 14 = 28
Since the sum equals the number itself, 28 is a perfect number.
The input consists of a single integer num, and the output should be:
trueifnumis a perfect numberfalseotherwise
The constraints tell us:
1 <= num <= 10^8
This upper bound is important. A naive solution that checks every number from 1 to num - 1 would require up to 10^8 operations in the worst case, which is inefficient. We need a better approach.
An important mathematical observation is that divisors appear in pairs. For example, for 28:
2 × 14 = 284 × 7 = 28
If we find one divisor, we automatically know another. This allows us to check only up to √num, which significantly improves performance.
Several edge cases deserve attention:
num = 1is not a perfect number because it has no positive divisors excluding itself, so the sum is0, not1.- Perfect squares require careful handling to avoid counting the square root twice.
- Small non-perfect numbers such as
2or7should correctly returnfalse.
Approaches
Brute Force Approach
The most straightforward approach is to iterate through every integer from 1 to num - 1, check whether it divides num, and add it to a running sum if it does.
This works because it explicitly computes the sum of all proper divisors. If the final sum equals num, then the number is perfect.
However, this method is inefficient. In the worst case, we check nearly 10^8 numbers, which is unnecessarily expensive given the problem constraints.
Key Insight for an Optimal Solution
The key mathematical insight is that divisors come in complementary pairs.
If d divides num, then num / d must also divide num.
For example, with 28:
- Finding divisor
2automatically gives divisor14 - Finding divisor
4automatically gives divisor7
This means we only need to search up to √num.
We initialize the divisor sum with 1, since 1 is always a proper divisor for numbers greater than 1. Then, for every divisor d between 2 and √num:
- Add
d - Add
num // d
If d * d == num, we only add the divisor once because both values are identical.
This reduces the time complexity from O(n) to O(√n).
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(1) | Checks every number from 1 to num - 1 |
| Optimal | O(√n) | O(1) | Uses divisor pairing to reduce work |
Algorithm Walkthrough
- Handle the special case for
1
Since 1 has no proper divisors, it cannot be a perfect number. Return false immediately.
2. Initialize the divisor sum
Start with divisor_sum = 1 because 1 is always a valid divisor for any number greater than 1.
3. Iterate from 2 to √num
We only need to check divisors up to the square root because every divisor larger than √num has a matching smaller divisor.
4. Check divisibility
For each candidate divisor d, check whether num % d == 0. If true, d is a divisor.
5. Add both divisors in the pair
Add d to the sum. Then compute the paired divisor:
paired_divisor = num // d
If d and paired_divisor are different, add both.
6. Avoid double-counting perfect squares
When d * d == num, the divisor pair is actually the same number. Add it only once.
7. Compare the sum to the original number
After processing all divisors, return whether divisor_sum == num.
Why it works
The algorithm works because every divisor of a number appears in a unique pair around √num. By iterating only up to the square root and adding both divisors together, we account for every proper divisor exactly once. The special handling for perfect squares ensures no divisor is counted twice. Therefore, the computed sum is exactly the sum of all proper divisors, which guarantees correctness.
Python Solution
class Solution:
def checkPerfectNumber(self, num: int) -> bool:
if num == 1:
return False
divisor_sum = 1
divisor = 2
while divisor * divisor <= num:
if num % divisor == 0:
divisor_sum += divisor
paired_divisor = num // divisor
if paired_divisor != divisor:
divisor_sum += paired_divisor
divisor += 1
return divisor_sum == num
The implementation begins by handling the special case where num == 1, since 1 cannot be perfect.
We initialize divisor_sum to 1 because every integer greater than 1 has 1 as a proper divisor. Then we iterate through possible divisors starting from 2.
The loop condition:
divisor * divisor <= num
ensures we only search up to √num.
Whenever we find a divisor, we add it to the running total and compute its paired divisor using integer division. If the paired divisor differs from the current divisor, we add it as well. This prevents double-counting in cases where num is a perfect square.
Finally, the function compares the accumulated divisor sum against num and returns the result.
Go Solution
func checkPerfectNumber(num int) bool {
if num == 1 {
return false
}
divisorSum := 1
for divisor := 2; divisor*divisor <= num; divisor++ {
if num%divisor == 0 {
divisorSum += divisor
pairedDivisor := num / divisor
if pairedDivisor != divisor {
divisorSum += pairedDivisor
}
}
}
return divisorSum == num
}
The Go implementation follows the exact same logic as the Python solution.
Since Go uses explicit variable declarations, we define divisorSum as an integer and use a standard for loop for iteration. Integer division is handled naturally using / because both operands are integers.
Integer overflow is not a concern here because num ≤ 10^8, and the divisor sum remains well within Go's int range.
Worked Examples
Example 1: num = 28
We initialize:
divisor_sum = 1
We check divisors from 2 to √28 ≈ 5.
| Divisor | Divides 28? | Paired Divisor | Updated Sum |
|---|---|---|---|
| 2 | Yes | 14 | 1 + 2 + 14 = 17 |
| 3 | No | - | 17 |
| 4 | Yes | 7 | 17 + 4 + 7 = 28 |
| 5 | No | - | 28 |
Final check:
divisor_sum = 28
Since 28 == 28, return true.
Example 2: num = 7
We initialize:
divisor_sum = 1
We check divisors from 2 to √7 ≈ 2.
| Divisor | Divides 7? | Paired Divisor | Updated Sum |
|---|---|---|---|
| 2 | No | - | 1 |
Final check:
divisor_sum = 1
Since 1 != 7, return false.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(√n) | We only check divisors up to the square root of num |
| Space | O(1) | Only a few variables are used |
The efficiency comes from the divisor pairing property. Instead of examining every integer below num, we only examine values up to √num, which dramatically reduces the number of iterations. The algorithm uses constant additional memory regardless of input size.
Test Cases
solution = Solution()
assert solution.checkPerfectNumber(28) is True # Provided example, perfect number
assert solution.checkPerfectNumber(7) is False # Provided example, prime number
assert solution.checkPerfectNumber(1) is False # Minimum boundary value
assert solution.checkPerfectNumber(2) is False # Smallest prime
assert solution.checkPerfectNumber(6) is True # Small perfect number
assert solution.checkPerfectNumber(496) is True # Larger perfect number
assert solution.checkPerfectNumber(8128) is True # Known perfect number
assert solution.checkPerfectNumber(16) is False # Perfect square, avoid double-counting sqrt
assert solution.checkPerfectNumber(25) is False # Another perfect square
assert solution.checkPerfectNumber(99999999) is False # Large non-perfect number
assert solution.checkPerfectNumber(100000000) is False # Maximum constraint value
| Test | Why |
|---|---|
28 |
Validates provided perfect number example |
7 |
Validates provided non-perfect example |
1 |
Tests smallest input and special case |
2 |
Tests small prime number |
6 |
Validates smallest perfect number |
496 |
Tests known larger perfect number |
8128 |
Tests another known perfect number |
16 |
Ensures perfect square divisors are not double-counted |
25 |
Additional perfect square validation |
99999999 |
Stress test with a large non-perfect number |
100000000 |
Tests upper constraint boundary |
Edge Cases
Input 1
The number 1 is a common source of bugs because many implementations initialize the divisor sum with 1. If we are not careful, the algorithm might incorrectly classify 1 as perfect.
Our implementation handles this explicitly by immediately returning false when num == 1.
Perfect Squares
Numbers such as 16 or 25 require careful treatment because the square root divisor appears only once.
For example:
16 = 4 × 4
If we blindly add both divisors in every pair, we would count 4 twice. The implementation prevents this with:
if paired_divisor != divisor:
This guarantees correctness.
Prime Numbers
Prime numbers such as 7 or 13 only have one proper divisor, 1.
A naive implementation may accidentally assume that every divisor search finds multiple factors. Our solution naturally handles primes because no divisors are found between 2 and √num, leaving the sum at 1, which correctly returns false.