Problem overview
The function isPrime receives a non-negative Int named n and returns true if it is a prime number, false otherwise. A prime is a number greater than 1 whose only divisors are 1 and itself. So 31 is prime, while 6 is not, because it is divisible by 2 and 3.
The statement asks for O(n) time and O(1) extra space. That allows a direct test of every possible divisor, with no extra storage.
Approach: look for a divisor between 2 and n - 1
Rephrase the definition: n is prime exactly when it is at least 2 and no number d with 1 < d < n divides it. That turns the question into a search for a counterexample, a divisor in the range 2 to n - 1:
- If some
ddividesn, the number is not prime, and the function can returnfalseimmediately. - If the loop finishes without finding a divisor, the number is prime.
A number d divides n when the remainder of n / d is zero, which Kotlin writes as n % d == 0.
Numbers below 2 need separate handling. The values 0 and 1 aren't prime by definition, but a loop over a range like 2 until n is empty for them, so without a guard they would fall through to true.
Step by step
- Handle the small cases first. If
nis less than 2, returnfalse. This covers 0 and 1, which are not prime. - Loop over the candidate divisors. Use an
Intvariabledin aforloop over2 until n. The range starts at 2 because every number is divisible by 1, and it excludesnbecause every number is divisible by itself. - Test each candidate. If
n % d == 0, thenddividesn, so returnfalseat once. - Return
trueafter the loop. No divisor was found, sonis prime.
The solution in Kotlin
class Solution {
fun isPrime(n: Int): Boolean {
if (n < 2) {
return false
}
for (d in 2 until n) {
if (n % d == 0) {
return false
}
}
return true
}
}
Kotlin details worth noting:
2 until nexcludesn. It produces2, 3, ..., n - 1. The form2..nwould includenand make every number look divisible by itself, so no number would ever be prime.- Empty ranges are fine. For
n = 2the range2 until 2contains nothing, so the loop body doesn't run and the function returnstrue. Forn = 3it contains only 2, which doesn't divide 3. No special case is needed for the smallest primes. - The remainder operator.
%returns the remainder of integer division.31 % 2is 1, so 2 doesn't divide 31, while6 % 2is 0. - Every path returns. The function ends with
return true, so the compiler sees a value on every path through theBooleanfunction.
Tracing two inputs
For n = 6 the loop tries d = 2 first. Since 6 % 2 is 0, the function returns false and never tests 3, 4 or 5. For n = 31 the loop tries every d from 2 to 30, and each remainder is non-zero, so the loop ends and the function returns true.
Complexity
Time: O(n). For a prime, or for a number whose smallest divisor is large, the loop runs n - 2 times. A number such as 100 stops at 2, since the first divisor found ends the function. The worst case is what the bound describes.
Space: O(1). The function keeps only the loop variable d.
The work can be reduced a lot. Divisors come in pairs, so if n has a divisor other than 1 and itself, it has one no larger than the square root of n. Checking only up to that point brings the time down to about O(sqrt(n)). This lesson uses the plain version to practise the loop.
A faster version
Here is the square-root idea as a loop. It is a variation for comparison, not the lesson's solution:
fun isPrimeFast(n: Int): Boolean {
if (n < 2) {
return false
}
var d = 2
while (d.toLong() * d <= n) {
if (n % d == 0) {
return false
}
d++
}
return true
}
The product d * d is computed as a Long on purpose. For n near the top of the Int range, an Int product would overflow and wrap to a negative number, which would break the comparison. This version answers true for 2147483647 after about 46,000 steps, where the plain loop needs about two billion.
Edge cases and common mistakes
- 0 and 1. Both return
falsethanks to then < 2guard. Without it they would returntrue, because the loop has nothing to check. - 2. The only even prime. It returns
truethrough the empty loop. - Starting the loop at 1. Every number is divisible by 1, so
n % 1 == 0would make the function returnfalsefor every input. - Including
nin the range. The same problem appears at the other end:n % nis 0. - Returning
trueinside the loop. Writingelse { return true }after the divisibility test decides from the first candidate only. For 9 it would test 2, see no divisor, and wrongly answertrue. - Large inputs. A very large prime near the top of the
Intrange, which is about 2.1 billion, makes this version do about two billion iterations. That's the cost of theO(n)approach.
Testing
n = 31givestrue.n = 6givesfalse.n = 2andn = 3givetrue.n = 0,n = 1andn = 4givefalse.n = 97givestrue, andn = 100givesfalse.