You are given an array of integers distance
.
You start at the point (0, 0)
on an X-Y plane, and you move distance[0]
meters to the north, then distance[1]
meters to the west, distance[2]
meters to the south, distance[3]
meters to the east, and so on. In other words, after each move, your direction changes counter-clockwise.
Return true
if your path crosses itself or false
if it does not.
Example 1:
Input: distance = [2,1,1,2] Output: true Explanation: The path crosses itself at the point (0, 1).
Example 2:
Input: distance = [1,2,3,4] Output: false Explanation: The path does not cross itself at any point.
Example 3:
Input: distance = [1,1,1,2,1] Output: true Explanation: The path crosses itself at the point (0, 0).
Constraints:
1 <= distance.length <= 105
1 <= distance[i] <= 105
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Imagine you're drawing a path one step at a time. The brute force approach is to simply check after each step if the current step causes the path to cross over itself at any previous location. We do this by remembering every line segment we've drawn so far and checking if the newest one intersects any of them.
Here's how the algorithm would work step-by-step:
def is_self_crossing(distance): path = []
x_coordinate = 0
y_coordinate = 0
for step_distance in distance:
# Determine the next coordinate based on the current direction and distance
if len(path) % 4 == 0:
next_x_coordinate = x_coordinate
next_y_coordinate = y_coordinate + step_distance
elif len(path) % 4 == 1:
next_x_coordinate = x_coordinate - step_distance
next_y_coordinate = y_coordinate
elif len(path) % 4 == 2:
next_x_coordinate = x_coordinate
next_y_coordinate = y_coordinate - step_distance
else:
next_x_coordinate = x_coordinate + step_distance
next_y_coordinate = y_coordinate
new_segment = ((x_coordinate, y_coordinate), (next_x_coordinate, next_y_coordinate))
# Check for intersection with existing segments.
for index in range(len(path)):
existing_segment = path[index]
if do_intersect(new_segment, existing_segment):
return True
#Add the new segment to the path
path.append(new_segment)
x_coordinate = next_x_coordinate
y_coordinate = next_y_coordinate
return False
def orientation(point1, point2, point3):
value = (float(point2[1] - point1[1]) * (point3[0] - point2[0])) - \
(float(point2[0] - point1[0]) * (point3[1] - point2[1]))
if (value > 0):
return 1 # Clockwise
elif (value < 0):
return -1 # Counterclockwise
else:
return 0 # Collinear
def on_segment(point1, point2, point3):
if ( (point2[0] <= max(point1[0], point3[0])) and (point2[0] >= min(point1[0], point3[0])) and \
(point2[1] <= max(point1[1], point3[1])) and (point2[1] >= min(point1[1], point3[1]))):
return True
return False
def do_intersect(segment1, segment2):
point1, point2 = segment1
point3, point4 = segment2
orientation1 = orientation(point1, point2, point3)
orientation2 = orientation(point1, point2, point4)
orientation3 = orientation(point3, point4, point1)
orientation4 = orientation(point3, point4, point2)
# General case
if ((orientation1 != orientation2) and (orientation3 != orientation4)):
return True
# Special Cases
# point1, point2 and point3 are colinear and point3 lies on segment point1point2
if ((orientation1 == 0) and on_segment(point1, point3, point2)):
return True
# point1, point2 and point4 are colinear and point4 lies on segment point1point2
if ((orientation2 == 0) and on_segment(point1, point4, point2)):
return True
# point3, point4 and point1 are colinear and point1 lies on segment point3point4
if ((orientation3 == 0) and on_segment(point3, point1, point4)):
return True
# point3, point4 and point2 are colinear and point2 lies on segment point3point4
if ((orientation4 == 0) and on_segment(point3, point2, point4)):
return True
return False
The core idea is to detect self-crossing by recognizing specific geometric patterns as we draw the path. We don't simulate the entire path; instead, we look for immediate crossing conditions by checking only a few previous steps.
Here's how the algorithm would work step-by-step:
def is_self_crossing(distance):
path_length = len(distance)
for i in range(3, path_length):
# Check for crossing with the line two steps back
if distance[i] >= distance[i - 2]:
return True
# Check for crossing with the line three steps back
if i >= 4 and distance[i - 1] == distance[i - 3] and distance[i] + distance[i - 4] >= distance[i - 2]:
return True
# Scenario 3: Crossing 4 segments back
if i >= 5 and distance[i - 1] <= distance[i - 3] and distance[i - 2] <= distance[i - 4] and distance[i] + distance[i - 4] >= distance[i - 2] and distance[i - 1] + distance[i - 5] >= distance[i - 3]:
return True
return False
Case | How to Handle |
---|---|
Null or empty input array | Return false immediately as no self-crossing is possible with no steps. |
Array with fewer than 4 elements | Return false, as at least 4 moves are required to potentially form a self-crossing. |
Array with only increasing values (e.g., [1,2,3,4,5]) | Return false as there's no chance of inward spiraling and crossing. |
Immediate crossing, like [1, 1, 1, 1] | The core logic should detect and return true correctly. |
Near misses, where the path gets very close but doesn't cross | Ensure sufficient precision in determining overlaps to avoid false negatives. |
Large integer values in the input array, causing potential overflow in calculations. | Use appropriate data types (e.g., long) or modulo arithmetic to prevent integer overflow issues during coordinate calculations. |
Spiral that extends very far before crossing | Ensure the solution's space complexity for storing path history doesn't become excessive, potentially by using a sliding window approach. |
Input array with a large number of elements representing a very complex spiral | The solution's time complexity must remain reasonable; avoid brute force methods and prioritize efficient geometric checks. |