Start learning free

LESSON 88 OF 414 · CONDITIONAL STATEMENTS - CHALLENGES

Interval Intersection in Scala: O(1) Check with Negation

Practice Read the full explanation

Given two closed intervals [A1, B1] and [A2, B2], check and return if the two intervals intersect each other.

A closed interval [a, b] (with a <= b) denotes the set of real numbers x with a <= x <= b.

Two closed intervals are said to intersect if they have common numbers, i.e. that appear in both intervals.

Example 1:

Input: A1 = 1, B1 = 6, A2 = 3, B2 = 8
Output: true

Example 2:

Input: A1 = 1, B1 = 4, A2 = 4, B2 = 6
Output: true

Example 3:

Input: A1 = 1, B1 = 8, A2 = 4, B2 = 6
Output: true

Example 4:

Input: A1 = 4, B1 = 6, A2 = 1, B2 = 3
Output: false

Note:

Your algorithm should run in O(1) time and use O(1) extra space.


Hints:

Instead of thinking about when the intervals intersect each other, it's easier to think about when they do not intersect each other.

The intervals do not intersect each other when one of them is "to the left" of the other one i.e. B1 < A2 or B2 < A1.

We can write an if statement checking if B1 < A2 or B2 < A1. If so, we return false. Otherwise, we return true.

You can implement this using no else statements. You can even implement it in one line using only one return.
STEP 1 OF 1
    Ctrl + Enter to check

    SCALA · CONDITIONAL STATEMENTS - CHALLENGES

    Interval Intersection in Scala, explained

    Problem overview

    The method intersect receives two closed intervals, [a1, b1] and [a2, b2], as four integers. A closed interval [a, b], with a <= b, contains every number x such that a <= x <= b, both ends included. The method returns true when the two intervals share at least one number, and false when they share none.

    Four cases show the possibilities:

    • [1, 6] and [3, 8] overlap on 3 to 6, so the answer is true.
    • [1, 4] and [4, 6] only touch at 4. Closed intervals include their ends, so 4 belongs to both, and the answer is true.
    • [1, 8] and [4, 6]: the second sits entirely inside the first, so the answer is true.
    • [4, 6] and [1, 3]: the second ends at 3, before the first starts at 4, so the answer is false.

    The statement asks for O(1) time and O(1) extra space, so the answer has to come from a few comparisons, not from listing the numbers in each interval.

    Approach: describe the ways to miss

    Intervals can overlap in many ways: partly from the left, partly from the right, one inside the other, one equal to the other. Listing all those is tedious. Missing each other has only two ways:

    • the first interval is entirely to the left of the second: b1 < a2;
    • the second interval is entirely to the left of the first: b2 < a1.

    If neither holds, the intervals must share a number, so they intersect. The answer is the negation of "one of them is entirely to the left of the other".

    Step by step

    The lesson has one step, made of these parts:

    1. Write the two ways to miss: b1 < a2 and b2 < a1.
    2. Join them with ||. Either one is enough for the intervals to be apart. Put the pair in parentheses.
    3. Negate with !. The negation is true exactly when neither miss condition holds, so it is the answer.
    4. Return it in one return statement. The expression is already a Boolean, so no if or else is needed.

    The solution in Scala

    class Solution {
        def intersect(a1: Int, b1: Int, a2: Int, b2: Int): Boolean = {
            return !(b1 < a2 || b2 < a1)
        }
    }
    

    The Scala details worth knowing:

    • The parentheses matter. ! binds tighter than ||. Without the parentheses, !b1 < a2 || b2 < a1 would try to apply ! to the integer b1 and fail to compile. The parentheses make ! apply to the whole condition.
    • De Morgan's law. !(p || q) equals !p && !q. So the same method can be written as a2 <= b1 && a1 <= b2: "the second starts no later than the first ends, and the first starts no later than the second ends". Negating < gives >=, which turns into <= when the sides are swapped.
    • The return is optional. The body's last expression is the result, so !(b1 < a2 || b2 < a1) alone would do.

    Complexity

    Time: O(1). The method makes two comparisons, one OR and one negation, whatever the size of the numbers. Space: O(1). No extra data is stored.

    Edge cases and common mistakes

    • Touching intervals. [1, 3] and [3, 5] share the point 3. The miss conditions use a strict <, so 3 < 3 is false and the intervals are reported as intersecting. Using <= would wrongly reject them.
    • One inside the other. For [1, 8] and [4, 6], neither 8 < 4 nor 6 < 1 holds, so the answer is true.
    • Single-point intervals. [5, 5] and [5, 5] intersect, because 5 < 5 is false both ways.
    • Argument order. The signature is intersect(a1, b1, a2, b2), with both ends of the first interval before the second. Mixing the order up turns the check into something else.
    • Checking only one miss. Testing only b1 < a2 misses the case where the second interval lies entirely to the left of the first.

    Testing

    • intersect(1, 6, 3, 8) gives true.
    • intersect(1, 4, 4, 6) gives true.
    • intersect(1, 8, 4, 6) gives true.
    • intersect(4, 6, 1, 3) gives false.
    • intersect(1, 2, 5, 6) gives false.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free