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:
- The current line crosses the line from 3 steps ago
- The current line crosses the line from 4 steps ago
- 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.
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]
andd[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]
andd[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 agod[i-2]
- The previous line
d[i-1]
is short enough compared to the line from 3 steps agod[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 agod[i-1] <= d[i-3]
: The previous line is no longer than the one from 3 steps agod[i] >= d[i-2] - d[i-4]
: The current line is long enough to reach the crossing pointd[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 EvaluatorExample 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 crossingdistance = [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.
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!