Algorithm tutorials
Computational Geometry Basics: Cross Product, Segments, Polygons and Convex Hull
Exact integer geometry for interviews: dot and cross products, orientation tests, segment intersection including touching and collinear cases, the shoelace area formula, ray-casting point-in-polygon, and Andrew's monotone chain convex hull with or without collinear boundary points.
Interview geometry is mostly about one operation: the 2D cross product. It tells you which way three points turn, whether two segments cross, how large a polygon is, and which points form the convex hull. With integer coordinates, all of it can be done exactly, without a single floating-point comparison.
Intuition
Treat points as vectors. For vectors u = (ux, uy) and v = (vx, vy):
- Dot product
ux*vx + uy*vy = |u| |v| cos(angle): positive for an acute angle, zero for perpendicular, negative for obtuse. Useful for projections and right-angle checks. - Cross product
ux*vy - uy*vx = |u| |v| sin(angle): the signed area of the parallelogram spanned byuandv. Positive whenvis counter-clockwise fromu, negative when clockwise, zero when they are parallel.
For three points, cross(o, a, b) is the cross product of a - o and b - o. Its sign is the orientation: a left turn (counter-clockwise), a right turn, or collinear. Everything below is built from that one sign.
- Segment intersection: segments AB and CD properly cross iff C and D are on opposite sides of line AB and A and B are on opposite sides of line CD. Touching and overlapping cases reduce to "is an endpoint on the other segment".
- Polygon area (shoelace formula): summing
cross(origin, p[i], p[i+1])over the edges gives twice the signed area; it is positive for counter-clockwise vertex order. - Point in polygon (ray casting): shoot a ray to the right; it crosses the boundary an odd number of times iff the point is inside.
- Convex hull (Andrew's monotone chain): sort points by x, then sweep left to right building the lower hull and right to left building the upper hull, popping the last point whenever it would make a clockwise turn.
When to use it in interviews / OAs
- "Do these segments/rectangles intersect", "is the polygon convex", "are these points collinear".
- "Area of a polygon given its vertices", "smallest fence around all trees" (convex hull).
- "Max points on a line" (orientation or normalized slopes), "valid square", "minimum area rectangle" (dot/cross products and squared distances).
- Any time a solution compares slopes or angles with floats: rewrite it with cross products.
Template: primitives
def cross(o, a, b) -> int:
# cross product of (a - o) and (b - o): > 0 left turn, < 0 right turn, 0 collinear
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
def dot(o, a, b) -> int:
# dot product of (a - o) and (b - o): > 0 acute angle at o, 0 right angle, < 0 obtuse
return (a[0] - o[0]) * (b[0] - o[0]) + (a[1] - o[1]) * (b[1] - o[1])
def orientation(a, b, c) -> int:
v = cross(a, b, c)
return (v > 0) - (v < 0)
def on_segment(p, a, b) -> bool:
# p lies on the closed segment a-b
return (cross(a, b, p) == 0
and min(a[0], b[0]) <= p[0] <= max(a[0], b[0])
and min(a[1], b[1]) <= p[1] <= max(a[1], b[1]))
def segments_intersect(p1, p2, p3, p4) -> bool:
d1, d2 = orientation(p3, p4, p1), orientation(p3, p4, p2)
d3, d4 = orientation(p1, p2, p3), orientation(p1, p2, p4)
if d1 * d2 < 0 and d3 * d4 < 0:
return True # a proper crossing
# otherwise they meet only if an endpoint lies on the other segment
return on_segment(p1, p3, p4) or on_segment(p2, p3, p4) or on_segment(p3, p1, p2) or on_segment(p4, p1, p2)
Template: polygons
def polygon_area2(poly) -> int:
# twice the signed area (shoelace formula); positive for counter-clockwise order
n = len(poly)
return sum(poly[i][0] * poly[(i + 1) % n][1] - poly[(i + 1) % n][0] * poly[i][1] for i in range(n))
def polygon_area(poly) -> float:
return abs(polygon_area2(poly)) / 2
def point_in_polygon(p, poly) -> int:
# 1 inside, 0 on the boundary, -1 outside; works for any simple polygon
inside = False
n = len(poly)
for i in range(n):
a, b = poly[i], poly[(i + 1) % n]
if on_segment(p, a, b):
return 0
if (a[1] > p[1]) != (b[1] > p[1]): # the edge straddles the horizontal line through p
# the crossing is to the right of p iff p is left of the upward-directed edge
if (cross(a, b, p) > 0) == (b[1] > a[1]):
inside = not inside
return 1 if inside else -1
The half-open test (a.y > p.y) != (b.y > p.y) counts a vertex lying exactly on the ray once, not twice, which is the usual source of ray-casting bugs.
Template: convex hull
def convex_hull(points, keep_collinear: bool = False):
# Andrew's monotone chain; hull vertices in counter-clockwise order
pts = sorted(set(points))
if len(pts) <= 2:
return pts
limit = 0 if keep_collinear else 1 # pop on clockwise turns (and on straight ones unless kept)
def chain(seq):
out = []
for p in seq:
while len(out) >= 2 and cross(out[-2], out[-1], p) < limit:
out.pop()
out.append(p)
return out
lower, upper = chain(pts), chain(reversed(pts))
hull = lower[:-1] + upper[:-1] # each chain ends where the other one starts
return list(dict.fromkeys(hull)) # all-collinear input makes the chains overlap
keep_collinear=True keeps points that lie on hull edges (needed when every fence post on the boundary must be reported); the default returns only the corners.
Complexity
Orientation, intersection and on-segment tests are O(1). Area and point-in-polygon are O(n) for n vertices. The convex hull is O(n log n) for the sort plus O(n) for the two sweeps, since every point is pushed and popped at most once per chain.
Common pitfalls
- Floating point: comparing slopes or angles with
==fails; integer cross products are exact. If coordinates are up to 10^9, products reach 10^18, which still fits in a signed 64-bit integer, but sums of them may not. - Vertical lines: slope-based code needs special cases; cross products do not.
- Collinear and touching cases: segments that share an endpoint or overlap along a line are intersections; test them with
on_segment. - Orientation of the polygon: the shoelace sum is negative for clockwise input; take the absolute value for the area.
- Hull degeneracies: duplicate points, fewer than three points, or all points on one line. Deduplicate first and decide whether collinear boundary points belong in the answer.
- Ray casting through vertices: use the half-open rule above instead of counting both edges at a shared vertex.
Interview variations
- Erect the fence (all trees on the boundary): convex hull with collinear points kept.
- Check if it is a straight line: every
cross(p0, p1, pi)is zero. - Convex polygon check: all consecutive cross products have the same sign.
- Valid square: among the six squared distances, four equal sides and two equal, longer diagonals.
- Rectangle overlap / area: axis-aligned boxes overlap iff their x-ranges and y-ranges both overlap.
- Minimum area rectangle from points: diagonal pairs with equal midpoints and lengths (or perpendicular sides via dot products).
- Max points on a line: count collinear points per anchor with normalized directions or cross products.