Skip to main content
Back to problems
#3625
Hard Algorithms

Count number of trapezoids ii

Array Hash Table Math Geometry
40.1% acceptance
Feb 25, 2026
259
98
You are given a 2D integer array points where points[i] = [xi, yi] represents the coordinates of the ith point on the Cartesian plane. Return the number of unique trapezoids that can be formed by choosing any four distinct points from points. A trapezoid is a convex quadrilateral with at least one pair of parallel sides. Two lines are parallel if and only if they have the same slope.

Solution

Rust
Time O(n³)
Space O(n)
LeetCode
solution.rs
impl Solution {
  pub fn count_trapezoids(points: Vec<Vec<i32>>) -> i32 {
    // Strategy:
    //   answer = (quadruples with >=1 parallel pair)
    //          = sum_over_slopes [ pairs_of_distinct_parallel_lines(slope) ] - parallelograms
    //
    // For each slope s, group points by line. A line is uniquely identified by
    // (normalized_slope, intercept) where intercept = ndy*x - ndx*y.
    // For each slope, the count of valid "one parallel pair" quadruples involving
    // two DISTINCT parallel lines L_i, L_j is:
    //   sum_{i<j} C(|L_i|,2) * C(|L_j|,2)
    // This avoids collinear points (3+ on the same line) being counted.
    //
    // Each true parallelogram contributes to two different slopes, so subtract once.
    // Parallelograms are counted via the midpoint trick (diagonals bisect each other).
    // The midpoint trick can overcount when all 4 points are collinear, but such
    // collinear groups are never counted in trap_count, so we subtract collinear
    // pseudo-parallelograms back out.

    use std::collections::{HashMap, HashSet};
    let n = points.len();

    fn gcd(a: i32, b: i32) -> i32 { if b == 0 { a } else { gcd(b, a % b) } }

    // Normalize direction vector so (dx>0) or (dx==0 && dy>0)
    fn normalize(dy: i32, dx: i32) -> (i32, i32) {
      if dx == 0 { return (1, 0); }
      if dy == 0 { return (0, 1); }
      let g = gcd(dy.abs(), dx.abs());
      let (dy, dx) = (dy / g, dx / g);
      if dx < 0 { (-dy, -dx) } else { (dy, dx) }
    }

    let c2 = |x: i64| x * (x - 1) / 2;

    // Map each line (slope, intercept) -> set of point indices on it
    // intercept = ndy * x0 - ndx * y0 (constant for all points on the line)
    let mut line_pts: HashMap<((i32, i32), i64), HashSet<usize>> = HashMap::new();
    for i in 0..n {
      for j in i + 1..n {
        let dy = points[j][1] - points[i][1];
        let dx = points[j][0] - points[i][0];
        let (ndy, ndx) = normalize(dy, dx);
        let intercept =
          ndy as i64 * points[i][0] as i64 - ndx as i64 * points[i][1] as i64;
        let entry = line_pts.entry(((ndy, ndx), intercept)).or_default();
        entry.insert(i);
        entry.insert(j);
      }
    }

    // For each slope, accumulate the point-counts per line
    let mut slope_line_counts: HashMap<(i32, i32), Vec<i64>> = HashMap::new();
    for ((slope, _), pts) in &line_pts {
      slope_line_counts
        .entry(*slope)
        .or_default()
        .push(pts.len() as i64);
    }

    // trap_count = sum over slopes: sum_{i<j} C(mi,2)*C(mj,2)
    // = sum over slopes: [ (sum_i C(mi,2))^2 - sum_i C(mi,2)^2 ] / 2
    let mut trap_count: i64 = 0;
    for counts in slope_line_counts.values() {
      let total: i64 = counts.iter().map(|&c| c2(c)).sum();
      let sq_sum: i64 = counts.iter().map(|&c| { let x = c2(c); x * x }).sum();
      trap_count += (total * total - sq_sum) / 2;
    }

    // Count true parallelograms via midpoint trick:
    // Two pairs sharing a midpoint form a parallelogram (diagonals bisect each other).
    // Overcount only happens when all 4 points are collinear, which we correct below.
    let mut mid_count: HashMap<(i64, i64), i64> = HashMap::new();
    for i in 0..n {
      for j in i + 1..n {
        let mx = points[i][0] as i64 + points[j][0] as i64;
        let my = points[i][1] as i64 + points[j][1] as i64;
        *mid_count.entry((mx, my)).or_insert(0) += 1;
      }
    }
    let para_raw: i64 = mid_count.values().map(|&m| c2(m)).sum();

    // Subtract collinear pseudo-parallelograms:
    // For each line with m>=4 points, count pairs of pairs on that line
    // whose point-sum (== 2*midpoint) is equal.
    let mut collinear_pseudo: i64 = 0;
    for pts_set in line_pts.values() {
      let pts: Vec<usize> = pts_set.iter().copied().collect();
      let m = pts.len();
      if m >= 4 {
        let mut sum_cnt: HashMap<(i64, i64), i64> = HashMap::new();
        for ii in 0..m {
          for jj in ii + 1..m {
            let sx = points[pts[ii]][0] as i64 + points[pts[jj]][0] as i64;
            let sy = points[pts[ii]][1] as i64 + points[pts[jj]][1] as i64;
            *sum_cnt.entry((sx, sy)).or_insert(0) += 1;
          }
        }
        collinear_pseudo += sum_cnt.values().map(|&c| c2(c)).sum::<i64>();
      }
    }

    let true_para = para_raw - collinear_pseudo;
    (trap_count - true_para) as i32
  }
}