Facebook Pixel

335. Self Crossing

Problem Description

You start at position (0, 0) on a 2D coordinate plane. You're given an array distance containing integers that represent how far you move in each step.

The movement pattern follows these directions in order:

  • distance[0]: Move north (positive y-direction)
  • distance[1]: Move west (negative x-direction)
  • distance[2]: Move south (negative y-direction)
  • distance[3]: Move east (positive x-direction)
  • distance[4]: Move north again, and so on...

The direction changes counter-clockwise after each move, cycling through: North → West → South → East → North...

Your task is to determine if the path you trace ever crosses itself. Return true if any two line segments of your path intersect (excluding shared endpoints), and false otherwise.

For example:

  • If distance = [2, 1, 1, 2], you would move 2 units north, 1 unit west, 1 unit south, and 2 units east. This creates a path that crosses itself when the fourth segment intersects with the first segment.
  • If distance = [1, 2, 3, 4], the path would not cross itself.

The solution checks for three specific patterns where crossings can occur:

  1. The current line crosses the line from 3 steps ago
  2. The current line crosses the line from 4 steps ago
  3. The current line crosses the line from 5 steps ago

These are the only possible crossing scenarios due to the counter-clockwise movement pattern, as lines that are 2 steps apart are parallel and cannot cross.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we trace the path, we need to think about when line segments can possibly intersect. Since we're moving in a counter-clockwise pattern (North → West → South → East → North...), certain lines can never cross each other:

  • Lines that are 1 step apart are perpendicular and share an endpoint, so they can't cross
  • Lines that are 2 steps apart are parallel (both horizontal or both vertical), so they can't cross

This means the earliest possible crossing is between the current line and the line from 3 steps ago. For example, if we're drawing the 4th line (index 3), it could potentially cross the 1st line (index 0).

Let's visualize the three possible crossing patterns:

Pattern 1: Current line crosses line from 3 steps ago

  • This happens when we form a shape that curves back on itself after 4 moves
  • Condition: d[i] >= d[i-2] and d[i-1] <= d[i-3]
  • The current line is long enough to reach or pass the parallel line from 2 steps ago, AND the previous line is short enough that it doesn't push us too far away

Pattern 2: Current line crosses line from 4 steps ago

  • This occurs when we have 5 lines and the path wraps around
  • Condition: d[i-1] == d[i-3] and d[i] + d[i-4] >= d[i-2]
  • The lines from 1 and 3 steps ago align perfectly, and the current line plus the line from 4 steps ago can reach across

Pattern 3: Current line crosses line from 5 steps ago

  • This is the most complex pattern, happening with 6 or more lines
  • Multiple conditions must align for the lines to be in positions where they can intersect
  • We need to check relative positions and lengths of multiple segments

The key insight is that due to the counter-clockwise movement pattern, these are the ONLY three ways lines can cross. We don't need to check every pair of lines - just these specific relationships. Once we move beyond 5 steps ago, the geometric constraints of the counter-clockwise pattern make it impossible for those older lines to be crossed by the current line without having already triggered one of these three patterns.

Solution Approach

The implementation iterates through the distance array starting from index 3 (since we need at least 4 lines to have a crossing). For each position i, we check the three possible crossing patterns:

Pattern 1: 4-line crossing (current crosses 3 steps ago)

if d[i] >= d[i - 2] and d[i - 1] <= d[i - 3]:
    return True

This checks if:

  • The current line d[i] is long enough to reach or exceed the parallel line from 2 steps ago d[i-2]
  • The previous line d[i-1] is short enough compared to the line from 3 steps ago d[i-3]

When both conditions are met, the current line will cross the line from 3 steps ago.

Pattern 2: 5-line crossing (current crosses 4 steps ago)

if i >= 4 and d[i - 1] == d[i - 3] and d[i] + d[i - 4] >= d[i - 2]:
    return True

This requires i >= 4 (at least 5 lines) and checks if:

  • The lines from 1 and 3 steps ago have equal length d[i-1] == d[i-3], creating alignment
  • The sum of the current line and the line from 4 steps ago can span across d[i] + d[i-4] >= d[i-2]

This forms a specific geometric configuration where the path wraps around and crosses itself.

Pattern 3: 6-line crossing (current crosses 5 steps ago)

if (i >= 5 
    and d[i - 2] >= d[i - 4]
    and d[i - 1] <= d[i - 3]
    and d[i] >= d[i - 2] - d[i - 4]
    and d[i - 1] + d[i - 5] >= d[i - 3]):
    return True

This requires i >= 5 (at least 6 lines) and checks multiple conditions:

  • d[i-2] >= d[i-4]: The line from 2 steps ago is at least as long as the one from 4 steps ago
  • d[i-1] <= d[i-3]: The previous line is no longer than the one from 3 steps ago
  • d[i] >= d[i-2] - d[i-4]: The current line is long enough to reach the crossing point
  • d[i-1] + d[i-5] >= d[i-3]: The combined length creates the crossing opportunity

If none of these three patterns are detected throughout the entire array, the path doesn't cross itself, and we return false.

The algorithm runs in O(n) time where n is the length of the distance array, as we check each position once with constant-time comparisons. The space complexity is O(1) since we only use the input array and a few variables.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the path with distance = [2, 1, 1, 2] to see how we detect a crossing.

Step-by-step path construction:

  • Start at (0, 0)
  • Move North 2 units: (0, 0)(0, 2) — Line 0
  • Move West 1 unit: (0, 2)(-1, 2) — Line 1
  • Move South 1 unit: (-1, 2)(-1, 1) — Line 2
  • Move East 2 units: (-1, 1)(1, 1) — Line 3

Checking for crossings:

When i = 3 (drawing the 4th line), we check Pattern 1:

  • Is d[3] >= d[1]? Check: 2 >= 1
  • Is d[2] <= d[0]? Check: 1 <= 2

Both conditions are satisfied! This means Line 3 (the horizontal line from (-1, 1) to (1, 1)) crosses Line 0 (the vertical line from (0, 0) to (0, 2)). The intersection occurs at point (0, 1).

The function returns true immediately.


Now let's verify a non-crossing example with distance = [1, 2, 3, 4]:

Step-by-step path construction:

  • Start at (0, 0)
  • Move North 1 unit: (0, 0)(0, 1) — Line 0
  • Move West 2 units: (0, 1)(-2, 1) — Line 1
  • Move South 3 units: (-2, 1)(-2, -2) — Line 2
  • Move East 4 units: (-2, -2)(2, -2) — Line 3

Checking for crossings:

When i = 3, check Pattern 1:

  • Is d[3] >= d[1]? Check: 4 >= 2
  • Is d[2] <= d[0]? Check: 3 <= 1

The second condition fails. Line 2 is too long (3 units) compared to Line 0 (1 unit), which pushes Line 3 too far down to cross Line 0. The lines don't intersect.

Since we only have 4 distances, we can't check Patterns 2 or 3 (which require at least 5 and 6 lines respectively). The function returns false.

Solution Implementation

1class Solution:
2    def isSelfCrossing(self, distance: List[int]) -> bool:
3        """
4        Determines if a path crosses itself when moving in directions: 
5        north, west, south, east, repeatedly.
6      
7        Args:
8            distance: List of distances for each move
9          
10        Returns:
11            True if the path crosses itself, False otherwise
12        """
13      
14        # Need at least 4 moves to have a crossing
15        for i in range(3, len(distance)):
16            # Case 1: 4th line crosses 1st line
17            # Current line crosses the line 3 steps back
18            # This happens when we move far enough in current direction
19            # and the perpendicular distance is small enough
20            if (i >= 3 and 
21                distance[i] >= distance[i - 2] and 
22                distance[i - 1] <= distance[i - 3]):
23                return True
24          
25            # Case 2: 5th line crosses 2nd line
26            # When the path forms a specific pattern where lines align
27            # and the combined distances create a crossing
28            if (i >= 4 and 
29                distance[i - 1] == distance[i - 3] and 
30                distance[i] + distance[i - 4] >= distance[i - 2]):
31                return True
32          
33            # Case 3: 6th line crosses 3rd line
34            # Most complex case involving 6 consecutive moves
35            # Multiple conditions must be met for this type of crossing
36            if (i >= 5 and
37                distance[i - 2] >= distance[i - 4] and
38                distance[i - 1] <= distance[i - 3] and
39                distance[i] >= distance[i - 2] - distance[i - 4] and
40                distance[i - 1] + distance[i - 5] >= distance[i - 3]):
41                return True
42      
43        # No crossing detected
44        return False
45
1class Solution {
2    /**
3     * Determines if a path crosses itself when moving in a spiral pattern.
4     * The path starts moving north, then west, south, east, and repeats.
5     * 
6     * @param distance Array of distances for each move
7     * @return true if the path crosses itself, false otherwise
8     */
9    public boolean isSelfCrossing(int[] distance) {
10        // Iterate through the distance array starting from the 4th element
11        for (int i = 3; i < distance.length; i++) {
12            // Case 1: Fourth line crosses the first line
13            // Check if current line crosses the line from 3 steps ago
14            if (distance[i] >= distance[i - 2] && distance[i - 1] <= distance[i - 3]) {
15                return true;
16            }
17          
18            // Case 2: Fifth line crosses the second line
19            // Check if current line crosses the line from 4 steps ago
20            if (i >= 4 && 
21                distance[i - 1] == distance[i - 3] && 
22                distance[i] + distance[i - 4] >= distance[i - 2]) {
23                return true;
24            }
25          
26            // Case 3: Sixth line crosses the third line
27            // Check if current line crosses the line from 5 steps ago
28            if (i >= 5 && 
29                distance[i - 2] >= distance[i - 4] && 
30                distance[i - 1] <= distance[i - 3] &&
31                distance[i] >= distance[i - 2] - distance[i - 4] && 
32                distance[i - 1] + distance[i - 5] >= distance[i - 3]) {
33                return true;
34            }
35        }
36      
37        // No crossing detected
38        return false;
39    }
40}
41
1class Solution {
2public:
3    bool isSelfCrossing(vector<int>& distance) {
4        // For a path to self-cross, we need at least 4 lines
5        for (int i = 3; i < distance.size(); ++i) {
6            // Case 1: 4th line crosses 1st line
7            // The current line (i) crosses the line (i-3)
8            // This happens when:
9            // - Current line is long enough to reach back (distance[i] >= distance[i-2])
10            // - Previous line doesn't extend far enough (distance[i-1] <= distance[i-3])
11            if (distance[i] >= distance[i - 2] && distance[i - 1] <= distance[i - 3]) {
12                return true;
13            }
14          
15            // Case 2: 5th line crosses 2nd line
16            // Check if we have at least 5 lines and the 5th line crosses the 2nd line
17            // This happens when lines form a specific pattern where:
18            // - The previous line equals the line 3 steps back
19            // - Current line plus line 4 steps back reaches or exceeds line 2 steps back
20            if (i >= 4 && 
21                distance[i - 1] == distance[i - 3] && 
22                distance[i] + distance[i - 4] >= distance[i - 2]) {
23                return true;
24            }
25          
26            // Case 3: 6th line crosses 3rd line
27            // Check if we have at least 6 lines and the 6th line crosses the 3rd line
28            // This is the most complex case with multiple conditions:
29            // - Line (i-2) is at least as long as line (i-4)
30            // - Line (i-1) doesn't extend beyond line (i-3)
31            // - Current line is within a specific range
32            // - The sum of lines forms a crossing pattern
33            if (i >= 5 && 
34                distance[i - 2] >= distance[i - 4] && 
35                distance[i - 1] <= distance[i - 3] && 
36                distance[i] >= distance[i - 2] - distance[i - 4] && 
37                distance[i - 1] + distance[i - 5] >= distance[i - 3]) {
38                return true;
39            }
40        }
41      
42        // No crossing detected
43        return false;
44    }
45};
46
1function isSelfCrossing(distance: number[]): boolean {
2    // For a path to self-cross, we need at least 4 lines
3    for (let i = 3; i < distance.length; i++) {
4        // Case 1: 4th line crosses 1st line
5        // The current line (i) crosses the line (i-3)
6        // This happens when:
7        // - Current line is long enough to reach back (distance[i] >= distance[i-2])
8        // - Previous line doesn't extend far enough (distance[i-1] <= distance[i-3])
9        if (distance[i] >= distance[i - 2] && distance[i - 1] <= distance[i - 3]) {
10            return true;
11        }
12      
13        // Case 2: 5th line crosses 2nd line
14        // Check if we have at least 5 lines and the 5th line crosses the 2nd line
15        // This happens when lines form a specific pattern where:
16        // - The previous line equals the line 3 steps back
17        // - Current line plus line 4 steps back reaches or exceeds line 2 steps back
18        if (i >= 4 && 
19            distance[i - 1] === distance[i - 3] && 
20            distance[i] + distance[i - 4] >= distance[i - 2]) {
21            return true;
22        }
23      
24        // Case 3: 6th line crosses 3rd line
25        // Check if we have at least 6 lines and the 6th line crosses the 3rd line
26        // This is the most complex case with multiple conditions:
27        // - Line (i-2) is at least as long as line (i-4)
28        // - Line (i-1) doesn't extend beyond line (i-3)
29        // - Current line is within a specific range
30        // - The sum of lines forms a crossing pattern
31        if (i >= 5 && 
32            distance[i - 2] >= distance[i - 4] && 
33            distance[i - 1] <= distance[i - 3] && 
34            distance[i] >= distance[i - 2] - distance[i - 4] && 
35            distance[i - 1] + distance[i - 5] >= distance[i - 3]) {
36            return true;
37        }
38    }
39  
40    // No crossing detected
41    return false;
42}
43

Time and Space Complexity

Time Complexity: O(n) where n is the length of the distance array. The algorithm iterates through the distance array once with a single for loop starting from index 3 to the end. Within each iteration, it performs constant-time comparisons and arithmetic operations. Even though there are multiple conditional checks (3 different if statements), each check takes O(1) time as they only involve accessing array elements at specific indices and performing comparisons. Therefore, the overall time complexity is linear with respect to the input size.

Space Complexity: O(1). The algorithm uses only a constant amount of extra space. The variable d is just a reference to the input array distance, not a copy, so it doesn't consume additional space proportional to the input. The only other variable used is the loop counter i, which requires constant space. No additional data structures like arrays, lists, or recursive call stacks are created during execution.

Common Pitfalls

1. Overlooking the 5-line and 6-line crossing patterns

Many developers initially focus only on the 4-line crossing pattern (Case 1) because it's the most intuitive - when the current line directly crosses a line from 3 steps ago. However, this misses critical crossing scenarios.

Why this happens: The 4-line pattern seems complete because we're checking if perpendicular lines cross. It's easy to assume that checking lines 3 steps apart covers all cases since lines 2 steps apart are parallel.

The problem: Without checking 5-line and 6-line patterns, the solution fails on cases like:

  • distance = [1, 1, 2, 1, 1] - Creates a 5-line crossing
  • distance = [1, 1, 2, 2, 3, 1] - Creates a 6-line crossing

Solution: Always implement all three crossing patterns. Each represents a geometrically distinct way the path can intersect itself.

2. Incorrect boundary conditions in the 6-line pattern

The 6-line crossing has the most complex conditions, and getting any comparison operator wrong causes failures.

Common mistakes:

  • Using distance[i] > distance[i - 2] - distance[i - 4] instead of >=
  • Using distance[i - 1] + distance[i - 5] > distance[i - 3] instead of >=

Why this matters: The equality cases represent valid crossings where lines exactly meet at endpoints. Missing these edge cases causes the algorithm to return false negatives.

Solution: Carefully verify each inequality. Draw out the geometric configuration to understand why each condition needs >= rather than > for boundary cases.

3. Not checking array bounds properly

Each pattern requires a minimum number of lines to exist:

  • Case 1 needs at least 4 lines (i >= 3)
  • Case 2 needs at least 5 lines (i >= 4)
  • Case 3 needs at least 6 lines (i >= 5)

Common mistake: Starting the loop at index 0 or checking Case 2/3 without verifying enough elements exist, causing index out of bounds errors.

Solution: Start the main loop at index 3 and add explicit guards i >= 4 and i >= 5 before checking the respective patterns.

4. Misunderstanding the movement pattern

Common misconception: Assuming the directions are clockwise (North → East → South → West) instead of counter-clockwise (North → West → South → East).

Why this matters: The crossing detection logic is built on the specific counter-clockwise pattern. Using the wrong mental model leads to incorrect condition formulation.

Solution: Visualize or trace through small examples to internalize the counter-clockwise pattern before implementing the crossing checks.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More