Graham's Scan Algorithm & Convex Hull
Theory
A convex hull is the smallest convex polygon that can contain all points in a given set. Think of it as wrapping a rubber band around all the points - the shape it forms is the convex hull.
Key Concepts
-
Convex Polygon: A polygon where:
- All interior angles are less than 180 degrees
- Any line segment between two points inside the polygon lies entirely inside the polygon
-
Properties:
- Every point in the set is either inside the hull or on its boundary
- The hull vertices are a subset of the original points
- The hull is unique for a given set of points
Graham's Scan Algorithm Steps
-
Find Anchor Point
- Select the point with lowest y-coordinate (leftmost point if tie)
- This point is guaranteed to be on the hull
-
Sort Points
- Sort remaining points by polar angle relative to anchor point
- If angles are equal, sort by distance from anchor
-
Build Hull
- Push first three points onto stack
- For each remaining point:
- While the last three points make a non-left turn, pop the middle point
- Push current point
-
Handle Collinear Points
- Special care needed when multiple points have same angle
- Include all collinear points in final hull
Implementation
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
// Compute cross product to determine orientation
const crossProduct = (p, q, r) => (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x);
// Compute squared distance between two points
const distSq = (p1, p2) => (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2;
// Graham's Scan algorithm
const convexHull = (points) => {
if (points.length < 3) return points;
// Find pivot (lowest y, then lowest x if tie)
const pivot = points.reduce((a, b) => (b.y < a.y || (b.y === a.y && b.x < a.x) ? b : a));
// Sort by polar angle (remove collinear points)
points.sort((a, b) => {
let order = crossProduct(pivot, a, b);
return order === 0 ? distSq(pivot, a) - distSq(pivot, b) : -order;
});
// Construct hull using stack
return points.reduce((hull, p) => {
while (hull.length > 1 && crossProduct(hull[hull.length - 2], hull[hull.length - 1], p) <= 0) {
hull.pop();
}
hull.push(p);
return hull;
}, []);
};
// Example usage
const points = [
new Point(0, 3), new Point(1, 1), new Point(2, 2),
new Point(4, 4), new Point(0, 0), new Point(1, 2),
new Point(3, 1), new Point(3, 3)
];
const convexHullPoints = convexHull(points);
console.log("Convex Hull Points:", convexHullPoints.map(p => `(${p.x}, ${p.y})`));
Time & Space Complexity
- Time Complexity: O(n log n)
- Sorting points takes O(n log n)
- Building hull takes O(n)
- Space Complexity: O(n)
- Stack storage
- Sorted array storage
LeetCode Problems Using Convex Hull
-
587. Erect the Fence (Hard)
- Direct application of convex hull
- Need to handle collinear points carefully
-
1937. Maximum Number of Points with Cost (Medium)
- Can be optimized using convex hull concept
- Dynamic programming with geometric properties
-
483. Smallest Good Base (Hard)
- While not directly using convex hull, uses similar geometric concepts
- Binary search with geometric properties
-
456. 132 Pattern (Medium)
- Can be solved using monotonic stack (similar principle to Graham's Scan)
- Stack maintenance similar to convex hull building
Common Variations & Extensions
-
Monotone Chain Algorithm
- Alternative to Graham's Scan
- Builds upper and lower hulls separately
- Often simpler to implement
-
Dynamic Convex Hull
- Maintain hull as points are added/removed
- More complex but useful for streaming data
-
Closest Pair of Points
- Can be solved using convex hull techniques
- Often combined with divide-and-conquer
Tips for Implementation
-
Handle Edge Cases
- Less than 3 points
- Collinear points
- Duplicate points
-
Numerical Precision
- Use appropriate epsilon for floating-point comparisons
- Consider integer-only implementations when possible
-
Optimization Opportunities
- Pre-compute angles/distances
- Use array instead of stack for better cache performance
- Consider removing redundant calculations
Common Mistakes to Avoid
- Not handling collinear points correctly
- Incorrect anchor point selection
- Wrong turn detection implementation
- Not handling edge cases
- Poor floating-point comparisons