Hard

题目描述

给你一个二维整数数组 points,其中 points[i] = [xi, yi] 表示第 i 个点在笛卡尔坐标平面上的坐标。

返回可以通过从 points 中选择任意四个不同点形成的唯一梯形的数量。

梯形是至少有一对平行边的凸四边形。两条线平行当且仅当它们具有相同的斜率。

示例 1:

输入:points = [[-3,2],[3,0],[2,3],[3,2],[2,-3]]
输出:2
解释:
有两种不同的方式选择四个点形成梯形:
- 点 [-3,2], [2,3], [3,2], [2,-3] 形成一个梯形。
- 点 [2,3], [3,2], [3,0], [2,-3] 形成另一个梯形。

示例 2:

输入:points = [[0,0],[1,0],[0,1],[2,1]]
输出:1
解释:
只有一个梯形可以形成。

约束条件:

  • 4 <= points.length <= 500
  • -1000 <= xi, yi <= 1000
  • 所有点都是两两不同的。

解题思路

解题思路

这道题要求计算能够形成梯形的四点组合数。梯形的关键特征是至少有一对平行边,即至少有两条边具有相同的斜率。

核心思路:

  1. 斜率计算与标准化:对于任意两点,计算其连线的斜率。为避免浮点精度问题,使用 (dy, dx) 的最简分数形式表示斜率,通过最大公约数(GCD)进行约简,并统一符号。

  2. 分组统计:将所有点对按斜率分组。对于每个斜率组,如果有 k 条线段,则可以选择 C(k,2) = k*(k-1)/2 种方式作为梯形的平行边。

  3. 排除共端点情况:两条线段如果共享端点,无法构成四边形,需要排除这些情况。

  4. 处理平行四边形重复计数:平行四边形有两对平行边,会被计算两次,需要减去重复计数。平行四边形的判定条件是四个点形成的对角线中点重合。

算法步骤:

  • 枚举所有点对,计算标准化斜率
  • 按斜率分组,统计每组的有效线段对数
  • 识别并减去平行四边形的重复计数
  • 返回最终的梯形数量

时间复杂度为 O(n²),空间复杂度为 O(n²)。

代码实现

class Solution {
public:
    int countTrapezoids(vector<vector<int>>& points) {
        int n = points.size();
        map<pair<int, int>, vector<pair<int, int>>> slopeMap;
        
        auto gcd = [](int a, int b) {
            return b == 0 ? a : __gcd(abs(a), abs(b));
        };
        
        auto normalize = [&](int dy, int dx) -> pair<int, int> {
            if (dx == 0) return {1, 0};
            if (dy == 0) return {0, 1};
            int g = gcd(dy, dx);
            dy /= g;
            dx /= g;
            if (dx < 0) {
                dy = -dy;
                dx = -dx;
            }
            return {dy, dx};
        };
        
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int dy = points[j][1] - points[i][1];
                int dx = points[j][0] - points[i][0];
                auto slope = normalize(dy, dx);
                slopeMap[slope].push_back({i, j});
            }
        }
        
        long long result = 0;
        
        for (auto& [slope, segments] : slopeMap) {
            int k = segments.size();
            if (k < 2) continue;
            
            long long total = (long long)k * (k - 1) / 2;
            
            set<pair<int, int>> endpoints;
            for (auto& seg : segments) {
                endpoints.insert(seg);
            }
            
            for (int i = 0; i < k; i++) {
                for (int j = i + 1; j < k; j++) {
                    auto& seg1 = segments[i];
                    auto& seg2 = segments[j];
                    if (seg1.first == seg2.first || seg1.first == seg2.second ||
                        seg1.second == seg2.first || seg1.second == seg2.second) {
                        total--;
                    }
                }
            }
            
            result += total;
        }
        
        map<pair<int, int>, int> midpointCount;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int mx = points[i][0] + points[j][0];
                int my = points[i][1] + points[j][1];
                midpointCount[{mx, my}]++;
            }
        }
        
        for (auto& [midpoint, count] : midpointCount) {
            if (count >= 2) {
                result -= (long long)count * (count - 1) / 2;
            }
        }
        
        return result;
    }
};
class Solution:
    def countTrapezoids(self, points: List[List[int]]) -> int:
        from collections import defaultdict
        from math import gcd
        
        n = len(points)
        slope_map = defaultdict(list)
        
        def normalize_slope(dy, dx):
            if dx == 0:
                return (1, 0)
            if dy == 0:
                return (0, 1)
            g = gcd(abs(dy), abs(dx))
            dy //= g
            dx //= g
            if dx < 0:
                dy, dx = -dy, -dx
            return (dy, dx)
        
        for i in range(n):
            for j in range(i + 1, n):
                dy = points[j][1] - points[i][1]
                dx = points[j][0] - points[i][0]
                slope = normalize_slope(dy, dx)
                slope_map[slope].append((i, j))
        
        result = 0
        
        for slope, segments in slope_map.items():
            k = len(segments)
            if k < 2:
                continue
            
            total = k * (k - 1) // 2
            
            for i in range(k):
                for j in range(i + 1, k):
                    seg1 = segments[i]
                    seg2 = segments[j]
                    if (seg1[0] == seg2[0] or seg1[0] == seg2[1] or
                        seg1[1] == seg2[0] or seg1[1] == seg2[1]):
                        total -= 1
            
            result += total
        
        midpoint_count = defaultdict(int)
        for i in range(n):
            for j in range(i + 1, n):
                mx = points[i][0] + points[j][0]
                my = points[i][1] + points[j][1]
                midpoint_count[(mx, my)] += 1
        
        for count in midpoint_count.values():
            if count >= 2:
                result -= count * (count - 1) // 2
        
        return result
public class Solution {
    public int CountTrapezoids(int[][] points) {
        int n = points.Length;
        var slopeMap = new Dictionary<(int, int), List<(int, int)>>();
        
        int Gcd(int a, int b) {
            a = Math.Abs(a);
            b = Math.Abs(b);
            while (b != 0) {
                int temp = b;
                b = a % b;
                a = temp;
            }
            return a;
        }
        
        (int, int) NormalizeSlope(int dy, int dx) {
            if (dx == 0) return (1, 0);
            if (dy == 0) return (0, 1);
            int g = Gcd(dy, dx);
            dy /= g;
            dx /= g;
            if (dx < 0) {
                dy = -dy;
                dx = -dx;
            }
            return (dy, dx);
        }
        
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int dy = points[j][1] - points[i][1];
                int dx = points[j][0] - points[i][0];
                var slope = NormalizeSlope(dy, dx);
                
                if (!slopeMap.ContainsKey(slope)) {
                    slopeMap[slope] = new List<(int, int)>();
                }
                slopeMap[slope].Add((i, j));
            }
        }
        
        long result = 0;
        
        foreach (var kvp in slopeMap) {
            var segments = kvp.Value;
            int k = segments.Count;
            if (k < 2) continue;
            
            long total = (long)k * (k - 1) / 2;
            
            for (int i = 0; i < k; i++) {
                for (int j = i + 1; j < k; j++) {
                    var seg1 = segments[i];
                    var seg2 = segments[j];
                    if (seg1.Item1 == seg2.Item1 || seg1.Item1 == seg2.Item2 ||
                        seg1.Item2 == seg2.Item1 || seg1.Item2 == seg2.Item2) {
                        total--;
                    }
                }
            }
            
            result += total;
        }
        
        var midpointCount = new Dictionary<(int, int), int>();
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int mx = points[i][0] + points[j][0];
                int my = points[i][1] + points[j][1];
                var midpoint = (mx, my);
                
                if (!midpointCount.ContainsKey(midpoint)) {
                    midpointCount[midpoint] = 0;
                }
                midpointCount[midpoint]++;
            }
        }
        
        foreach (var count in midpointCount.Values) {
            if (count >= 2) {
                result -= (long)count * (count - 1) / 2;
            }
        }
        
        return (int)result;
    }
}
var countTrapezoids = function(points) {
    const n = points.length;
    let count = 0;
    
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            const slopeMap = new Map();
            
            for (let k = 0; k < n; k++) {
                if (k === i || k === j) continue;
                
                const slope1 = getSlope(points[i], points[k]);
                const slope2 = getSlope(points[j], points[k]);
                
                const key = slope1 + "," + slope2;
                slopeMap.set(key, (slopeMap.get(key) || 0) + 1);
            }
            
            for (let k = j + 1; k < n; k++) {
                if (k === i) continue;
                
                for (let l = k + 1; l < n; l++) {
                    if (l === i || l === j) continue;
                    
                    if (isTrapezoid(points[i], points[j], points[k], points[l])) {
                        count++;
                    }
                }
            }
        }
    }
    
    return count / 3;
};

function getSlope(p1, p2) {
    if (p1[0] === p2[0]) return "inf";
    return (p2[1] - p1[1]) / (p2[0] - p1[0]);
}

function isTrapezoid(p1, p2, p3, p4) {
    const slopes = [
        getSlope(p1, p2),
        getSlope(p2, p3),
        getSlope(p3, p4),
        getSlope(p4, p1),
        getSlope(p1, p3),
        getSlope(p2, p4)
    ];
    
    return slopes[0] === slopes[2] || slopes[1] === slopes[3] || slopes[4] === slopes[0] || slopes[4] === slopes[1] || slopes[5] === slopes[0] || slopes[5] === slopes[1] || slopes[5] === slopes[2] || slopes[5] === slopes[3];
}
var countTrapezoids = function(points) {
    const n = points.length;
    let count = 0;
    
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            for (let k = j + 1; k < n; k++) {
                for (let l = k + 1; l < n; l++) {
                    if (isTrapezoid(points[i], points[j], points[k], points[l])) {
                        count++;
                    }
                }
            }
        }
    }
    
    return count;
};

function getSlope(p1, p2) {
    if (p1[0] === p2[0]) return Infinity;
    return (p2[1] - p1[1]) / (p2[0] - p1[0]);
}

function isTrapezoid(p1, p2, p3, p4) {
    const points = [p1, p2, p3, p4];
    const slopes = [];
    
    for (let i = 0; i < 4; i++) {
        for (let j = i + 1; j < 4; j++) {
            slopes.push(getSlope(points[i], points[j]));
        }
    }
    
    const slopeCount = new Map();
    for (let slope of slopes) {
        slopeCount.set(slope, (slopeCount.get(slope) || 0) + 1);
    }
    
    for (let count of slopeCount.values()) {
        if (count >= 2) return true;
    }
    
    return false;
}

复杂度分析

项目复杂度
时间复杂度O(n²)
空间复杂度O(n²)

其中 n 是点的数量。时间复杂度主要来自于枚举所有点对计算斜率,以及对每个斜率组内的线段对进行检查。空间复杂度来自于存储所有线段的斜率信息和中点信息。