Problem overview
The method perfectNumbers receives an array of integers nums and returns the perfect numbers among them, in the same order. A number is perfect when it equals the sum of its divisors that are smaller than itself. For 6, those divisors are 1, 2 and 3, and 1 + 2 + 3 = 6, so 6 is perfect. The number 1 is not perfect: it has no smaller divisors, so its sum is 0. A perfect number that appears twice is kept twice, and if no number is perfect the result is empty. The array has 1 to 20 numbers, each from 1 to 10,000.
For [6, 8, 28, 12] the answer is [6, 28]. The sum for 8 is 1 + 2 + 4 = 7, and for 12 it is 1 + 2 + 3 + 4 + 6 = 16, so neither matches. For [1, 5, 10] the sums are 0, 1 and 8, so the answer is []. For [496, 6, 6] every number is perfect, so all three stay.
Approach: one helper, one comparison
The lesson splits the job in two. The helper sumOfDivisors takes a single number n and returns the sum of the divisors of n that are smaller than n. The graded method perfectNumbers walks through the array and keeps every number that equals the helper's answer for it. The definition of a perfect number becomes a single comparison: this.sumOfDivisors(num) === num.
Inside the helper, try every candidate divisor d from 1 up to n - 1. A number d divides n exactly when n % d is 0, so each such d is added to a running total. Stopping at n - 1, with d < n, leaves n itself out, which is what "smaller than itself" asks for.
Step by step
- In
sumOfDivisors, declaretotalas 0. Forn = 1the loop never runs, and 0 is the right answer. - Loop
dfrom 1 whiled < n. The condition is what excludesnitself. - If
n % d === 0, adddtototal. A remainder of 0 meansddividesn. - Return
total. For 6 it is 6, for 8 it is 7. - In
perfectNumbers, createperfect, an emptynumber[]. - Loop over
numswithfor (const num of nums). A number that appears twice is visited twice. - Compare
this.sumOfDivisors(num)withnum, and pushnumwhen they are equal. - Return
perfectafter the loop. It stays empty if no number matched.
The solution in TypeScript
class Solution {
sumOfDivisors(n: number): number {
let total: number = 0;
for (let d = 1; d < n; d++) {
if (n % d === 0) {
total += d;
}
}
return total;
}
perfectNumbers(nums: number[]): number[] {
let perfect: number[] = [];
for (const num of nums) {
if (this.sumOfDivisors(num) === num) {
perfect.push(num);
}
}
return perfect;
}
}
Points worth knowing:
%is the remainder operator.10 % 3is 1 and6 % 3is 0. Every number gives remainder 0 when divided by 1, so 1 is always counted forn > 1.===compares numbers by value. Both sides are numbers here, so it does what you expect.- Calling the helper needs
this.. Without it the compiler reports that the name cannot be found. - The helper is called once per number. It does not remember earlier answers, and with at most 20 numbers up to 10,000 that is fast.
Tracing the helper on 12
For n = 12, d runs from 1 to 11. The remainder 12 % d is 0 for d = 1, 2, 3, 4 and 6, so those five values are added: 1, then 3, then 6, then 10, then 16. The other candidates, 5, 7, 8, 9, 10 and 11, leave a remainder and add nothing. The helper returns 16, which is not 12, so 12 is not perfect. A number whose divisor sum is bigger than itself, like 12, is not perfect any more than one whose sum is smaller, like 8.
Divisor sums of the examples
Calling the helper gives sumOfDivisors(6) = 6, sumOfDivisors(8) = 7, sumOfDivisors(12) = 16, sumOfDivisors(28) = 28, sumOfDivisors(1) = 0, sumOfDivisors(5) = 1 and sumOfDivisors(10) = 8. A sum equal to the number marks it as perfect, and 6 and 28 are the only ones in the first example. Within the range 1 to 10,000 the perfect numbers are 6, 28, 496 and 8128, and perfectNumbers([8128, 10000]) returns [8128].
Checking fewer candidates
No divisor of n other than n itself is bigger than n / 2, so the loop could stop there:
sumOfDivisors(n: number): number {
let total: number = 0;
for (let d = 1; d <= n / 2; d++) {
if (n % d === 0) {
total += d;
}
}
return total;
}
It returns the same sums: 6 for 6, 28 for 28, 0 for 1 and 14211 for 10000. The lesson's version tests every number below n, which is simpler to reason about and fast enough for inputs up to 10,000.
Edge cases and common mistakes
- Including
nitself. Withd <= nthe sum for 6 becomes 12, not 6, and no number would ever equal its sum. - Starting the loop at 0.
n % 0isNaNin JavaScript, not an error, so it adds nothing here, but 0 is not a divisor. The right start is 1. - The number 1. Its sum is 0, not 1, so it is not perfect. The empty loop gives that for free.
- Returning inside the loop. A
returninside theforwould stop at the first number. - Dropping duplicates. The task keeps a perfect number every time it appears, so there is no set or
includescheck.
Testing
[6, 8, 28, 12]gives[6, 28].[1, 5, 10]gives[].[496, 6, 6]gives[496, 6, 6].
