CoreTechED

Loading

Count Maximum Points on Same Line in C

Learn to find the maximum number of collinear points in C: naive O(n³) cross-product and O(n²) slope hashing approaches with code examples.

Count Maximum Points on Same Line in C

Count Maximum Points on the Same Line in C: A Comprehensive Tutorial

Given two arrays x[] and y[] where (x[i], y[i]) represents points on the Cartesian plane, find the maximum number of points that lie on a single straight line. This is a classic computational geometry problem that combines cross-product collinearity checks with hash-based slope grouping.

This tutorial provides an in-depth exploration of two approaches in C: the naive O(n³) brute force and the optimal O(n²) slope-hashing method. Complete, portable C99/C11 implementations are provided with full output examples.


Conceptual Introduction & Fundamentals

What is the Problem?

Given n points, find the largest subset that is collinear (lies on the same straight line).

Examples:

text

Input: x = [1, 2, 3], y = [1, 2, 3] Output: 3 (points (1,1), (2,2), (3,3)) Input: x = [-1, 0, 1, 2, 3, 3], y = [1, 0, 1, 2, 3, 4] Output: 4 (points (3,2), (4,1), (2,3), (1,4) — wait, these are from different x/y)

Let’s verify the second example with the actual arrays:

text

Points: (-1,1), (0,0), (1,1), (2,2), (3,3), (3,4) Line y = x contains: (-1,-1)? No. But (0,0), (1,1), (2,2), (3,3) → 4 points.

Why This Problem Matters

  • Computational geometry: Foundation for convex hull, line fitting, and clustering.
  • Slope normalization: Practical use of GCD for canonical forms.
  • Hash map with structured keys: Custom key encoding.
  • Overlap handling: Duplicate points require special treatment.
  • Interview preparation: Common algorithmic coding interview question.

Approaches Overview

ApproachTimeSpace
Naive cross productO(n³)O(1)
Slope hashingO(n²)O(n)

Key Concepts

  • Collinearity test: Three points A, B, C are collinear iff cross product (B-A) × (C-A) = 0.
  • Slope: dy / dx. Canonicalize by dividing both by gcd(|dx|, |dy|) and ensuring the sign is consistent.
  • Vertical lines: Handle dx == 0 separately (slope is infinite).
  • Overlapping points: Identical points don’t define a slope but always lie on any line through that location.

Syntax & Core Mechanics

Approach 1: Naive Cross-Product

For each pair (i, j), count how many points lie on that line.

c

#include <stdio.h> #include <stdlib.h> int maxPointsNaive(const int x[], const int y[], int n) { if (n <= 2) return n; int ans = 1; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (x[i] == x[j] && y[i] == y[j]) continue; /* Identical */ int cnt = 0; for (int k = 0; k < n; k++) { long long lhs = (long long)(y[j] - y[i]) * (x[k] - x[i]); long long rhs = (long long)(y[k] - y[i]) * (x[j] - x[i]); if (lhs == rhs) cnt++; } if (cnt > ans) ans = cnt; } } return ans; } int main(void) { int x[] = {1, 2, 3}; int y[] = {1, 2, 3}; printf("%d\n", maxPointsNaive(x, y, 3)); return 0; }

Expected output:

text

3

Time: O(n³), Space: O(1)

Approach 2: Slope Hashing

For each point i, compute the slope to every other point j > i. Group by normalized slope. Track vertical lines and overlaps separately.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_SLOPES 1024 typedef struct { int dy; int dx; int count; int used; } SlopeEntry; static int gcd(int a, int b) { while (b) { int t = a % b; a = b; b = t; } return a < 0 ? -a : a; } static int findOrInsert(SlopeEntry *map, int *size, int dy, int dx) { for (int i = 0; i < *size; i++) { if (map[i].dy == dy && map[i].dx == dx) { map[i].count++; return map[i].count; } } map[*size].dy = dy; map[*size].dx = dx; map[*size].count = 1; (*size)++; return 1; } int maxPointsHash(const int x[], const int y[], int n) { if (n <= 2) return n; int maxPoint = 1; for (int i = 0; i < n; i++) { SlopeEntry map[MAX_SLOPES]; int mapSize = 0; int overlap = 0, vertical = 0, curMax = 0; for (int j = i + 1; j < n; j++) { int xDif = x[j] - x[i]; int yDif = y[j] - y[i]; if (xDif == 0 && yDif == 0) { overlap++; } else if (xDif == 0) { vertical++; } else { int g = gcd(xDif < 0 ? -xDif : xDif, yDif < 0 ? -yDif : yDif); yDif /= g; xDif /= g; /* Normalize sign: make xDif positive */ if (xDif < 0) { xDif = -xDif; yDif = -yDif; } int c = findOrInsert(map, &mapSize, yDif, xDif); if (c > curMax) curMax = c; } if (vertical > curMax) curMax = vertical; } int total = curMax + overlap + 1; if (total > maxPoint) maxPoint = total; } return maxPoint; } int main(void) { int x[] = {-1, 0, 1, 2, 3, 3}; int y[] = {1, 0, 1, 2, 3, 4}; int n = sizeof(x) / sizeof(x[0]); printf("%d\n", maxPointsHash(x, y, n)); return 0; }

Expected output:

text

4

Time: O(n²), Space: O(n) per iteration

Why Slope Normalization Matters

Without normalization, (2, 4) and (1, 2) would be treated as different slopes even though they represent the same line direction.

Normalize:

  1. Divide both dy and dx by gcd(|dy|, |dx|).
  2. Ensure dx > 0; if dx < 0, negate both.

This gives a canonical key for each direction.


Full Practical Code Example with Explanation

This program compares both approaches, verifies their agreement, and exercises edge cases.

c

/** * max_points_on_line.c * Finds maximum number of collinear points. * Compares naive cross-product and slope hashing approaches. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #define MAX_LINE_LEN 100 #define MAX_SLOPES 2048 /* ============================================================ * SlopeEntry + GCD * ============================================================ */ typedef struct { int dy; int dx; int count; } SlopeEntry; static int gcdInt(int a, int b) { if (a < 0) a = -a; if (b < 0) b = -b; while (b) { int t = a % b; a = b; b = t; } return a; } static int findOrInsert(SlopeEntry *map, int *size, int dy, int dx) { for (int i = 0; i < *size; i++) { if (map[i].dy == dy && map[i].dx == dx) { map[i].count++; return map[i].count; } } map[*size].dy = dy; map[*size].dx = dx; map[*size].count = 1; (*size)++; return 1; } /* ============================================================ * Approach 1: Naive * ============================================================ */ int maxPointsNaive(const int x[], const int y[], int n) { if (n <= 2) return n; int ans = 1; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (x[i] == x[j] && y[i] == y[j]) continue; int cnt = 0; for (int k = 0; k < n; k++) { long long lhs = (long long)(y[j] - y[i]) * (x[k] - x[i]); long long rhs = (long long)(y[k] - y[i]) * (x[j] - x[i]); if (lhs == rhs) cnt++; } if (cnt > ans) ans = cnt; } } return ans; } /* ============================================================ * Approach 2: Slope Hashing * ============================================================ */ int maxPointsHash(const int x[], const int y[], int n) { if (n <= 2) return n; int maxPoint = 1; for (int i = 0; i < n; i++) { SlopeEntry map[MAX_SLOPES]; int mapSize = 0; int overlap = 0, vertical = 0, curMax = 0; for (int j = i + 1; j < n; j++) { int xDif = x[j] - x[i]; int yDif = y[j] - y[i]; if (xDif == 0 && yDif == 0) { overlap++; } else if (xDif == 0) { vertical++; } else { int g = gcdInt(xDif, yDif); yDif /= g; xDif /= g; if (xDif < 0) { xDif = -xDif; yDif = -yDif; } int c = findOrInsert(map, &mapSize, yDif, xDif); if (c > curMax) curMax = c; } if (vertical > curMax) curMax = vertical; } int total = curMax + overlap + 1; if (total > maxPoint) maxPoint = total; } return maxPoint; } /* ============================================================ * Utility * ============================================================ */ static void demoBasic(void) { printf("\n=== Sample Point Sets ===\n"); int x1[] = {1, 2, 3}; int y1[] = {1, 2, 3}; int x2[] = {-1, 0, 1, 2, 3, 3}; int y2[] = {1, 0, 1, 2, 3, 4}; int x3[] = {1, 1, 1, 1}; int y3[] = {1, 2, 3, 4}; int x4[] = {5, 5, 5, 5, 5}; int y4[] = {1, 2, 3, 4, 5}; struct { int *x; int *y; int n; const char *label; } tests[] = { {x1, y1, 3, "(1,1),(2,2),(3,3)"}, {x2, y2, 6, "6 points"}, {x3, y3, 4, "Vertical line x=1"}, {x4, y4, 5, "Horizontal line y=5"} }; int nt = sizeof(tests) / sizeof(tests[0]); for (int t = 0; t < nt; t++) { printf("\nTest %d: %s\n", t + 1, tests[t].label); int r1 = maxPointsNaive(tests[t].x, tests[t].y, tests[t].n); int r2 = maxPointsHash(tests[t].x, tests[t].y, tests[t].n); printf(" Naive: %d\n Hash: %d\n", r1, r2); printf(" Verify: %s\n", (r1 == r2) ? "PASS" : "FAIL"); } printf("\n"); } static void demoEdgeCases(void) { printf("\n=== Edge Cases ===\n"); /* Empty */ printf("Empty: naive=%d, hash=%d\n", maxPointsNaive(NULL, NULL, 0), maxPointsHash(NULL, NULL, 0)); /* Single point */ int x1[] = {5}; int y1[] = {7}; printf("Single: %d / %d\n", maxPointsNaive(x1, y1, 1), maxPointsHash(x1, y1, 1)); /* Two points */ int x2[] = {1, 2}; int y2[] = {3, 4}; printf("Two: %d / %d\n", maxPointsNaive(x2, y2, 2), maxPointsHash(x2, y2, 2)); /* All identical */ int x3[] = {5, 5, 5, 5}; int y3[] = {7, 7, 7, 7}; printf("All identical: %d / %d (expect 4)\n", maxPointsNaive(x3, y3, 4), maxPointsHash(x3, y3, 4)); /* All on horizontal */ int x4[] = {1, 2, 3, 4}; int y4[] = {5, 5, 5, 5}; printf("All horizontal: %d / %d (expect 4)\n", maxPointsNaive(x4, y4, 4), maxPointsHash(x4, y4, 4)); /* All on vertical */ int x5[] = {3, 3, 3, 3}; int y5[] = {1, 2, 3, 4}; printf("All vertical: %d / %d (expect 4)\n", maxPointsNaive(x5, y5, 4), maxPointsHash(x5, y5, 4)); /* Square corners */ int x6[] = {0, 0, 1, 1}; int y6[] = {0, 1, 0, 1}; printf("Square corners: %d / %d (expect 2)\n", maxPointsNaive(x6, y6, 4), maxPointsHash(x6, y6, 4)); /* Negative coordinates */ int x7[] = {-2, -1, 0, 1, 2}; int y7[] = {-4, -2, 0, 2, 4}; printf("Negatives, slope 2: %d / %d (expect 5)\n", maxPointsNaive(x7, y7, 5), maxPointsHash(x7, y7, 5)); /* Duplicate points mixed in */ int x8[] = {1, 1, 2, 3}; int y8[] = {1, 1, 2, 3}; printf("Duplicate point: %d / %d (expect 4)\n", maxPointsNaive(x8, y8, 4), maxPointsHash(x8, y8, 4)); } static void performanceTest(void) { printf("\n=== Performance Comparison ===\n"); /* Naive small */ int smallN = 300; int *sx = malloc((size_t)smallN * sizeof(int)); int *sy = malloc((size_t)smallN * sizeof(int)); if (!sx || !sy) { free(sx); free(sy); return; } srand(42); for (int i = 0; i < smallN; i++) { sx[i] = rand() % 100; sy[i] = rand() % 100; } clock_t start, end; double t; start = clock(); int r1 = maxPointsNaive(sx, sy, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Naive (n=%d): result=%d, time=%.3f ms\n", smallN, r1, t); start = clock(); int r2 = maxPointsHash(sx, sy, smallN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%d, time=%.3f ms\n", smallN, r2, t); printf("Verify: %s\n", (r1 == r2) ? "PASS" : "FAIL"); free(sx); free(sy); /* Hash large */ int largeN = 5000; int *lx = malloc((size_t)largeN * sizeof(int)); int *ly = malloc((size_t)largeN * sizeof(int)); if (!lx || !ly) { free(lx); free(ly); return; } for (int i = 0; i < largeN; i++) { lx[i] = rand() % 1000; ly[i] = rand() % 1000; } start = clock(); int r3 = maxPointsHash(lx, ly, largeN); end = clock(); t = ((double)(end - start)) / CLOCKS_PER_SEC * 1000.0; printf("Hash (n=%d): result=%d, time=%.3f ms\n", largeN, r3, t); free(lx); free(ly); } static int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) return -1; errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) return -1; if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) return -1; *outValue = (int)val; return 0; } static void demoInteractive(void) { int n; if (readInteger("Enter number of points (1-5000): ", &n) != 0 || n < 1 || n > 5000) { printf("Invalid size.\n"); return; } int *x = malloc((size_t)n * sizeof(int)); int *y = malloc((size_t)n * sizeof(int)); if (!x || !y) { free(x); free(y); return; } for (int i = 0; i < n; i++) { char p[40]; snprintf(p, sizeof(p), " Point %d x: ", i); if (readInteger(p, &x[i]) != 0) { free(x); free(y); return; } snprintf(p, sizeof(p), " Point %d y: ", i); if (readInteger(p, &y[i]) != 0) { free(x); free(y); return; } } printf("\nMax points on a line: %d\n", maxPointsHash(x, y, n)); free(x); free(y); } int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Maximum Points on the Same Line Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate Both Approaches\n"); printf("2. Edge Cases\n"); printf("3. Performance Comparison\n"); printf("4. Interactive\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demoBasic(); break; case 2: demoEdgeCases(); break; case 3: performanceTest(); break; case 4: demoInteractive(); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice.\n"); break; } } return EXIT_SUCCESS; }

Explanation of the Approaches

  1. Naive: For each pair (i, j), use cross product to test collinearity of every k. O(n³).
  2. Slope Hash: For each anchor i, compute canonical slope to every j. Group by slope. Track maximum group size + overlaps + 1. O(n²).

Expected Output (sample run)

text

=== Sample Point Sets === Test 1: (1,1),(2,2),(3,3) Naive: 3 Hash: 3 Verify: PASS Test 2: 6 points Naive: 4 Hash: 4 Verify: PASS Test 3: Vertical line x=1 Naive: 4 Hash: 4 Verify: PASS Test 4: Horizontal line y=5 Naive: 5 Hash: 5 Verify: PASS === Edge Cases === Empty: naive=0, hash=0 Single: 1 / 1 Two: 2 / 2 All identical: 4 / 4 (expect 4) All horizontal: 4 / 4 (expect 4) All vertical: 4 / 4 (expect 4) Square corners: 2 / 2 (expect 2) Negatives, slope 2: 5 / 5 (expect 5) Duplicate point: 4 / 4 (expect 4)

Common Pitfalls, Edge Cases & Memory Considerations

1. Using Floating-Point Slopes

Floating-point comparison is unreliable; use normalized integer ratios instead.

Solution: Reduce dy/dx by GCD and encode as (dy, dx) pair.

2. Forgetting to Normalize Signs

(1, 2) and (-1, -2) are the same slope.

Solution: Ensure dx > 0 by negating both if needed.

3. Vertical Lines (dx == 0)

Slopes involving vertical lines are undefined.

Solution: Track vertical counts separately.

4. Overlapping (Identical) Points

Identical points lie on every line through them.

Solution: Count overlaps separately and add them to the total.

5. Integer Overflow in Cross Product

(y[j] - y[i]) * (x[k] - x[i]) can overflow int.

Solution: Use long long.

6. Collision in Slope Map

Linear probing handles collisions; use a map or sort-based approach if needed.

7. Empty and Single-Point Cases

Return 0 and 1 respectively.

8. Two Points

Return 2 (trivially collinear).

9. Duplicate Handling in Naive

Skip identical (i, j) pairs.

c

if (x[i] == x[j] && y[i] == y[j]) continue;

10. Memory Considerations

  • Naive: O(1) extra space.
  • Slope hash: O(n) per anchor (cleared between iterations).
  • Use dynamic arrays for large point sets.
  • Free all allocated memory.

Summary & Best Practices

The maximum-collinear-points problem introduces canonical slope representation and its use in hashing. The O(n²) solution is optimal for general point sets.

Key Takeaways

  • Collinearity: cross product (B-A) × (C-A) = 0.
  • Slope: dy / dx reduced by gcd(|dy|, |dx|).
  • Normalize sign: dx > 0.
  • Handle vertical lines and overlaps separately.
  • Naive: O(n³).
  • Slope hash: O(n²) — preferred.

Best Practices

  1. Use slope hashing for O(n²) time.
  2. Normalize slopes by GCD to canonicalize.
  3. Handle vertical lines as a special case.
  4. Count overlapping points and add to the total.
  5. Use long long for cross products.
  6. Size the slope map based on n (avoid fixed limits for large inputs).
  7. Free allocated memory.
  8. Test edge cases: empty, single, duplicates, vertical, horizontal, negatives.
  9. Verify with the naive approach during testing.
  10. Profile for large point sets.

By mastering this problem, you’ll be prepared for line-fitting, clustering, and other geometric algorithms in computer graphics, vision, and data analysis.

Leave a Reply

Your email address will not be published. Required fields are marked *