Problem overview
The method isLeapYear receives a year as an Int and returns true if it is a leap year and false otherwise. The rules are:
- a year divisible by 4 is a leap year;
- except that a year divisible by 100 (1800, 1900, ...) is a leap year only if it is also divisible by 400.
The examples: 2020 is divisible by 4 and not by 100, so it is a leap year. 2021 is not divisible by 4, so it is not. 1968 is divisible by 4 and not by 100, so it is a leap year. 1900 is divisible by 100 but not by 400, so it is not. And 2000, divisible by 400, is a leap year.
Approach: check the most specific rule first
The three rules pile exceptions on top of each other: 4 is the general rule, 100 is an exception to it, and 400 is an exception to the exception. Testing the rules in the wrong order gives wrong answers. If you test divisibility by 4 first, 1900 is accepted at once, although it is not a leap year.
The safe order is from the most specific condition to the most general one:
- divisible by 400: leap;
- otherwise, divisible by 100: not leap;
- otherwise, divisible by 4: leap;
- otherwise: not leap.
Because each check returns when it matches, a year only reaches a later check if all the earlier ones failed. That is what lets each check be simple: when the code asks about 100, it already knows the year is not divisible by 400, and when it asks about 4, it knows the year is not a multiple of 100.
Step by step
- Check 400 first. If
year % 400 == 0, returntrue. The remainder operator%gives what is left after division, and a remainder of 0 means the division is exact. Such a year is also divisible by 100 and by 4, so testing it first lets 2000 count as a leap year before the century rule can reject it. - Check 100 next. If
year % 100 == 0, returnfalse. A year that reaches this line is a multiple of 100 but not of 400, like 1900. - Check 4. If
year % 4 == 0, returntrue. Only years that are not multiples of 100 get here, and for them divisibility by 4 is the whole rule. 2020 and 1968 return at this line. - Fall through to
false. A year that passed none of the checks, such as 2021, is not divisible by 4. Noelseis needed, because every earlier branch has already returned.
The solution in Scala
class Solution {
def isLeapYear(year: Int): Boolean = {
if (year % 400 == 0) {
return true
}
if (year % 100 == 0) {
return false
}
if (year % 4 == 0) {
return true
}
return false
}
}
The Scala details worth knowing:
- The remainder operator.
%works onIntlike in most languages. For negative numbers the result takes the sign of the left operand, so-4 % 400is-4, but a remainder of exactly 0 is still 0. - Early
return. Eachreturnends the method, and a method that usesreturnneeds its result type,Booleanhere. - A shorter form. The four checks collapse into one expression:
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0). It reads "divisible by 4, and not a century unless divisible by 400". It is equivalent, and you can write it without anyreturn.
Complexity
Time: O(1). The method does at most three remainder operations and comparisons, whatever the year is. Space: O(1). It stores no extra data.
The three-level rule exists because the average calendar year should be close to the real solar year, about 365.2425 days long. Adding a day every fourth year gives 365.25 on average, which is a little too much. Skipping three leap days every 400 years, the century years not divisible by 400, brings the average to exactly 365.2425, since 97 of every 400 years are leap years.
Edge cases and common mistakes
- Century years. 1900 and 2100 are not leap years. 2000 and 2400 are.
- Testing 4 first. Writing the checks in the order 4, 100, 400 returns
truefor 1900 straight away, which is wrong. - Testing 100 before 400. This returns
falsefor 2000, which is a leap year. - Using
=for comparison. The remainder test needs==. A single=is assignment, not comparison, and the compiler rejects it as a condition. - Forgetting the last return. The method must produce a
Booleanon every path, including years that fail all three checks.
Testing
isLeapYear(2020)givestrue.isLeapYear(2021)givesfalse.isLeapYear(1968)givestrue.isLeapYear(1900)givesfalse.isLeapYear(2000)givestrue.
