Facebook Pixel

149. Max Points on a Line

HardGeometryArrayHash TableMath
Leetcode Link

Problem Description

You are given an array of points on a 2D coordinate plane, where each point is represented as [x, y] coordinates. Your task is to find the maximum number of points that can lie on the same straight line.

The problem asks you to determine which subset of the given points forms the largest collinear group (points that all fall on a single straight line).

For example:

  • If you have points [[1,1], [2,2], [3,3]], all three points lie on the same line (y = x), so the answer would be 3
  • If you have points [[1,1], [3,2], [5,3], [4,1], [2,3], [1,4]], you need to check all possible lines and find which one contains the most points

The solution approach uses a triple nested loop to:

  1. Pick two points to define a potential line (points at indices i and j)
  2. Check all remaining points (at index k) to see if they lie on the same line
  3. Use the cross-multiplication technique to check collinearity: three points (x1,y1), (x2,y2), and (x3,y3) are collinear if (y2-y1) * (x3-x1) = (y3-y1) * (x2-x1)

This avoids division and potential floating-point precision issues when checking if points have the same slope. The algorithm keeps track of the maximum count of collinear points found across all possible lines.

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

Intuition

To find the maximum number of points on the same line, we need to think about what defines a line. A line is uniquely determined by any two distinct points on it. This gives us our starting point: we can enumerate all possible lines by considering every pair of points.

Once we have two points defining a line, we need to check which other points lie on this same line. The mathematical condition for three points to be collinear is that they must have the same slope between any two pairs.

The slope between points (x1, y1) and (x2, y2) is (y2 - y1) / (x2 - x1). For a third point (x3, y3) to be on the same line, we need: (y2 - y1) / (x2 - x1) = (y3 - y1) / (x3 - x1)

However, division can cause issues with floating-point precision and vertical lines (where the denominator would be zero). To avoid these problems, we can cross-multiply the equation: (y2 - y1) * (x3 - x1) = (y3 - y1) * (x2 - x1)

This gives us an elegant way to check collinearity using only integer arithmetic.

The brute force approach emerges naturally: for every possible pair of points that defines a line, count how many total points lie on that line. Keep track of the maximum count seen. Since we're already iterating through pairs with indices i and j, we only need to check points with index k > j to avoid redundant counting. The initial count starts at 2 (for the two points defining the line), and we increment it for each additional collinear point found.

Solution Approach

The implementation uses a triple nested loop structure to systematically check all possible lines formed by pairs of points and count how many points lie on each line.

Algorithm Steps:

  1. Initialize the result: Start with ans = 1 since at minimum, any single point forms a "line" with itself.

  2. First loop (index i): Iterate through each point as the first point of a potential line.

    • Extract coordinates (x1, y1) from points[i]
  3. Second loop (index j): For each first point, iterate through all subsequent points to form a line.

    • Start from j = i + 1 to avoid duplicate line checks
    • Extract coordinates (x2, y2) from points[j]
    • Initialize cnt = 2 since we already have two points on this line
  4. Third loop (index k): Check all remaining points to see if they're collinear with the first two.

    • Start from k = j + 1 to check only unprocessed points
    • Extract coordinates (x3, y3) from points[k]
    • Calculate a = (y2 - y1) * (x3 - x1)
    • Calculate b = (y3 - y1) * (x2 - x1)
    • If a == b, the three points are collinear, so increment cnt
  5. Update maximum: After checking all points for the current line defined by points i and j, update ans = max(ans, cnt)

  6. Return result: After examining all possible lines, return ans

Key Implementation Details:

  • The collinearity check (y2 - y1) * (x3 - x1) == (y3 - y1) * (x2 - x1) avoids division and floating-point errors
  • The nested loop indices i < j < k ensure each line is checked only once
  • The counter cnt accumulates using cnt += a == b, which adds 1 when the condition is true and 0 when false
  • Time complexity is O(n³) where n is the number of points
  • Space complexity is O(1) as we only use 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 walk through the solution with a concrete example: points = [[1,1], [2,2], [3,3], [1,3]]

Initial Setup:

  • We have 4 points. Let's visualize them:
    • Point 0: (1,1)
    • Point 1: (2,2)
    • Point 2: (3,3)
    • Point 3: (1,3)
  • Initialize ans = 1 (minimum possible answer)

Iteration 1: i=0 (first point = [1,1])

  • j=1 (second point = [2,2]): Define line through (1,1) and (2,2)

    • cnt = 2 (two points on the line so far)
    • k=2: Check if (3,3) is collinear
      • a = (2-1) * (3-1) = 1 * 2 = 2
      • b = (3-1) * (2-1) = 2 * 1 = 2
      • Since a == b, point (3,3) is on the line! cnt = 3
    • k=3: Check if (1,3) is collinear
      • a = (2-1) * (1-1) = 1 * 0 = 0
      • b = (3-1) * (2-1) = 2 * 1 = 2
      • Since a ≠ b, point (1,3) is NOT on the line. cnt remains 3
    • Update ans = max(1, 3) = 3
  • j=2 (second point = [3,3]): Define line through (1,1) and (3,3)

    • cnt = 2
    • k=3: Check if (1,3) is collinear
      • a = (3-1) * (1-1) = 2 * 0 = 0
      • b = (3-1) * (3-1) = 2 * 2 = 4
      • Since a ≠ b, point (1,3) is NOT on the line. cnt remains 2
    • Update ans = max(3, 2) = 3
  • j=3 (second point = [1,3]): Define line through (1,1) and (1,3)

    • cnt = 2
    • No more points to check (k would start at 4, which is out of bounds)
    • Update ans = max(3, 2) = 3

Iteration 2: i=1 (first point = [2,2])

  • j=2 (second point = [3,3]): Define line through (2,2) and (3,3)

    • cnt = 2
    • k=3: Check if (1,3) is collinear
      • a = (3-2) * (1-2) = 1 * (-1) = -1
      • b = (3-2) * (3-2) = 1 * 1 = 1
      • Since a ≠ b, point (1,3) is NOT on the line. cnt remains 2
    • Update ans = max(3, 2) = 3
  • j=3 (second point = [1,3]): Define line through (2,2) and (1,3)

    • cnt = 2
    • No more points to check
    • Update ans = max(3, 2) = 3

Iteration 3: i=2 (first point = [3,3])

  • j=3 (second point = [1,3]): Define line through (3,3) and (1,3)
    • cnt = 2
    • No more points to check
    • Update ans = max(3, 2) = 3

Result: The maximum number of points on a line is 3 (points [1,1], [2,2], and [3,3] all lie on the line y = x).

Solution Implementation

1class Solution:
2    def maxPoints(self, points: List[List[int]]) -> int:
3        # Get the total number of points
4        num_points = len(points)
5      
6        # Initialize the maximum count to 1 (at least one point exists)
7        max_points_on_line = 1
8      
9        # Iterate through each point as the first reference point
10        for i in range(num_points):
11            x1, y1 = points[i]
12          
13            # Iterate through each subsequent point as the second reference point
14            for j in range(i + 1, num_points):
15                x2, y2 = points[j]
16              
17                # Start with 2 points (the two reference points are always on the same line)
18                points_on_current_line = 2
19              
20                # Check all remaining points to see if they're collinear with the two reference points
21                for k in range(j + 1, num_points):
22                    x3, y3 = points[k]
23                  
24                    # Check collinearity using cross product
25                    # Points are collinear if (y2-y1)/(x2-x1) = (y3-y1)/(x3-x1)
26                    # To avoid division, we cross-multiply: (y2-y1)*(x3-x1) = (y3-y1)*(x2-x1)
27                    cross_product_left = (y2 - y1) * (x3 - x1)
28                    cross_product_right = (y3 - y1) * (x2 - x1)
29                  
30                    # If the cross products are equal, the point is on the same line
31                    if cross_product_left == cross_product_right:
32                        points_on_current_line += 1
33              
34                # Update the maximum count if current line has more points
35                max_points_on_line = max(max_points_on_line, points_on_current_line)
36      
37        return max_points_on_line
38
1class Solution {
2    public int maxPoints(int[][] points) {
3        int n = points.length;
4        int maxPointsOnLine = 1; // At least one point exists
5      
6        // Iterate through each point as the first point of a potential line
7        for (int i = 0; i < n; ++i) {
8            int x1 = points[i][0];
9            int y1 = points[i][1];
10          
11            // Iterate through each point after i as the second point to form a line
12            for (int j = i + 1; j < n; ++j) {
13                int x2 = points[j][0];
14                int y2 = points[j][1];
15                int pointsOnCurrentLine = 2; // Line through points i and j has at least 2 points
16              
17                // Check all remaining points to see if they lie on the same line
18                for (int k = j + 1; k < n; ++k) {
19                    int x3 = points[k][0];
20                    int y3 = points[k][1];
21                  
22                    // Check if point k is collinear with points i and j
23                    // Using cross product: if (y2-y1)/(x2-x1) = (y3-y1)/(x3-x1)
24                    // Cross multiply to avoid division: (y2-y1)*(x3-x1) = (y3-y1)*(x2-x1)
25                    int crossProduct1 = (y2 - y1) * (x3 - x1);
26                    int crossProduct2 = (y3 - y1) * (x2 - x1);
27                  
28                    if (crossProduct1 == crossProduct2) {
29                        ++pointsOnCurrentLine;
30                    }
31                }
32              
33                // Update the maximum number of points found on a single line
34                maxPointsOnLine = Math.max(maxPointsOnLine, pointsOnCurrentLine);
35            }
36        }
37      
38        return maxPointsOnLine;
39    }
40}
41
1class Solution {
2public:
3    int maxPoints(vector<vector<int>>& points) {
4        int numPoints = points.size();
5        int maxPointsOnLine = 1;  // At least one point always exists
6      
7        // Try each point as the first point of a potential line
8        for (int i = 0; i < numPoints; ++i) {
9            int x1 = points[i][0];
10            int y1 = points[i][1];
11          
12            // Try each subsequent point as the second point to form a line
13            for (int j = i + 1; j < numPoints; ++j) {
14                int x2 = points[j][0];
15                int y2 = points[j][1];
16              
17                // Start with 2 points (i and j) on the current line
18                int pointsOnCurrentLine = 2;
19              
20                // Check all remaining points to see if they're collinear
21                for (int k = j + 1; k < numPoints; ++k) {
22                    int x3 = points[k][0];
23                    int y3 = points[k][1];
24                  
25                    // Check collinearity using cross product
26                    // Points are collinear if (y2-y1)/(x2-x1) == (y3-y1)/(x3-x1)
27                    // To avoid division, we cross-multiply:
28                    // (y2-y1)*(x3-x1) == (y3-y1)*(x2-x1)
29                    int crossProduct1 = (y2 - y1) * (x3 - x1);
30                    int crossProduct2 = (y3 - y1) * (x2 - x1);
31                  
32                    // If cross products are equal, point k is on the same line
33                    if (crossProduct1 == crossProduct2) {
34                        pointsOnCurrentLine++;
35                    }
36                }
37              
38                // Update the maximum number of points found on any line
39                maxPointsOnLine = max(maxPointsOnLine, pointsOnCurrentLine);
40            }
41        }
42      
43        return maxPointsOnLine;
44    }
45};
46
1function maxPoints(points: number[][]): number {
2    const numPoints = points.length;
3    let maxPointsOnLine = 1; // At least one point always exists
4  
5    // Try each point as the first point of a potential line
6    for (let i = 0; i < numPoints; i++) {
7        const x1 = points[i][0];
8        const y1 = points[i][1];
9      
10        // Try each subsequent point as the second point to form a line
11        for (let j = i + 1; j < numPoints; j++) {
12            const x2 = points[j][0];
13            const y2 = points[j][1];
14          
15            // Start with 2 points (i and j) on the current line
16            let pointsOnCurrentLine = 2;
17          
18            // Check all remaining points to see if they're collinear
19            for (let k = j + 1; k < numPoints; k++) {
20                const x3 = points[k][0];
21                const y3 = points[k][1];
22              
23                // Check collinearity using cross product
24                // Points are collinear if (y2-y1)/(x2-x1) == (y3-y1)/(x3-x1)
25                // To avoid division, we cross-multiply:
26                // (y2-y1)*(x3-x1) == (y3-y1)*(x2-x1)
27                const crossProduct1 = (y2 - y1) * (x3 - x1);
28                const crossProduct2 = (y3 - y1) * (x2 - x1);
29              
30                // If cross products are equal, point k is on the same line
31                if (crossProduct1 === crossProduct2) {
32                    pointsOnCurrentLine++;
33                }
34            }
35          
36            // Update the maximum number of points found on any line
37            maxPointsOnLine = Math.max(maxPointsOnLine, pointsOnCurrentLine);
38        }
39    }
40  
41    return maxPointsOnLine;
42}
43

Time and Space Complexity

Time Complexity: O(n³)

The code uses three nested loops:

  • The outer loop runs n times (iterating through each point as the first point)
  • The middle loop runs from i+1 to n, which is at most n-1 iterations
  • The inner loop runs from j+1 to n, which is at most n-2 iterations

For each triple of points (i, j, k), the code performs constant time operations:

  • Extracting coordinates: O(1)
  • Computing a = (y2 - y1) * (x3 - x1): O(1)
  • Computing b = (y3 - y1) * (x2 - x1): O(1)
  • Comparing a == b: O(1)

The total number of iterations is approximately n * n * n / 6 (considering the constraints i < j < k), which simplifies to O(n³).

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space:

  • Variables n, ans: O(1)
  • Variables x1, y1, x2, y2, x3, y3: O(1)
  • Variables cnt, a, b: O(1)

No additional data structures are created that scale with the input size. The space used remains constant regardless of the number of points in the input.

Common Pitfalls

1. Handling Duplicate Points

The current solution doesn't explicitly handle duplicate points. If the input contains duplicate points (same x and y coordinates), they should all be counted as lying on the same line with any other point.

Problem Example:

points = [[1,1], [1,1], [2,2]]  # Two duplicate points at [1,1]

The current algorithm would treat these as separate points and might not count them correctly.

Solution: Pre-process the points to handle duplicates explicitly, or add special logic to detect and count duplicate points:

def maxPoints(self, points: List[List[int]]) -> int:
    if len(points) <= 2:
        return len(points)
  
    max_points_on_line = 1
  
    for i in range(len(points)):
        x1, y1 = points[i]
        duplicates = 1  # Count duplicates of current point
        local_max = 0
      
        for j in range(i + 1, len(points)):
            x2, y2 = points[j]
          
            # Check if it's a duplicate point
            if x1 == x2 and y1 == y2:
                duplicates += 1
                continue
          
            points_on_current_line = 2
          
            for k in range(j + 1, len(points)):
                x3, y3 = points[k]
                cross_product_left = (y2 - y1) * (x3 - x1)
                cross_product_right = (y3 - y1) * (x2 - x1)
              
                if cross_product_left == cross_product_right:
                    points_on_current_line += 1
          
            local_max = max(local_max, points_on_current_line)
      
        # Add duplicates to the maximum found for this base point
        max_points_on_line = max(max_points_on_line, local_max + duplicates - 1)
        # Handle case where all points from i onwards are duplicates
        max_points_on_line = max(max_points_on_line, duplicates)
  
    return max_points_on_line

2. Integer Overflow for Large Coordinates

When calculating cross products like (y2 - y1) * (x3 - x1), the multiplication of two large integers could potentially cause overflow in languages with fixed integer sizes.

Problem Example:

points = [[0,0], [10^9, 10^9], [2*10^9, 2*10^9]]

Solution: In Python, this isn't typically an issue due to arbitrary precision integers, but in other languages, you might need to:

  • Use long long or BigInteger types
  • Implement a GCD-based slope comparison to keep numbers smaller
  • Use floating-point with careful epsilon comparison (though this introduces precision issues)

3. Edge Cases with Small Input Sizes

The algorithm assumes at least 3 points for meaningful computation, but doesn't handle edge cases properly.

Problem Example:

points = [[1,1]]  # Single point
points = [[1,1], [2,2]]  # Two points

Solution: Add early return conditions:

def maxPoints(self, points: List[List[int]]) -> int:
    n = len(points)
  
    # Handle edge cases
    if n <= 2:
        return n
  
    # Rest of the original algorithm...
    max_points_on_line = 1
    # ...

4. Vertical and Horizontal Lines

While the cross-multiplication technique handles vertical lines (where x2 - x1 = 0) correctly without division by zero, developers might be tempted to optimize by calculating slopes directly, which would fail for vertical lines.

Why the current approach works: The formula (y2 - y1) * (x3 - x1) == (y3 - y1) * (x2 - x1) correctly handles:

  • Vertical lines: When x2 = x1, the formula becomes (y2 - y1) * (x3 - x1) == (y3 - y1) * 0, which is true only if x3 = x1
  • Horizontal lines: When y2 = y1, the formula becomes 0 * (x3 - x1) == (y3 - y1) * (x2 - x1), which is true only if y3 = y1

Avoid this mistake:

# DON'T DO THIS - will cause division by zero for vertical lines
slope1 = (y2 - y1) / (x2 - x1)  
slope2 = (y3 - y1) / (x3 - x1)
if slope1 == slope2:  # Also has floating-point precision issues
    points_on_current_line += 1
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

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

Load More