2025-12-03-LeetCode刷题笔记-3625-统计梯形的数目-II

题目信息


题目描述

给定一个二维整数数组 points,其中 points[i] = [xi, yi] 表示第 i 个点在笛卡尔平面上的坐标。你需要从 points 中任意选择四个互不相同的点,计算可以组成的梯形的数量。

梯形是一种至少有一对平行边的凸四边形。所有点都是两两不同的。


初步思路

  1. 枚举所有线段 : 遍历所有可能的点对 (p1, p2),将它们视为线段。
  2. 计算斜率和截距 : 对于每条线段,计算其斜率 k 和截距 b
    • 需要特殊处理垂直线(斜率无穷大)的情况,例如用 inf 或一个足够大的值表示。
    • 截距的计算也要考虑垂直线的情况。
  3. 统计平行线对 :
    • 使用一个 defaultdict(lambda: defaultdict(int)) 来存储 斜率 -> 截距 -> 该截距上的线段数量
    • 对于每个斜率 k,遍历其对应的截距 b 集合。如果某个截距 b 上有 c 条线段,那么从这些线段中选择两条可以构成一个梯形的一条边。
    • 通过组合数 c * (c - 1) / 2 统计形成平行线的组合数。
  4. 处理平行四边形重复计数 :
    • 梯形定义为"至少一对平行边",这意味着平行四边形(有两对平行边)会被计算两次。
    • 通过计算线段中点来识别平行四边形。如果两条线段具有相同的中点,且它们不共线,则它们构成一个平行四边形。
    • 使用另一个 defaultdict(lambda: defaultdict(int)) 来存储 中点坐标 -> 斜率 -> 经过该中点且具有该斜率的线段数量
    • 对于每个中点,遍历其对应的斜率集合。如果某个中点处,某个斜率 k 上有 c 条线段,那么从中选择两条可以构成一个平行四边形。
    • 从之前统计的梯形总数中减去平行四边形的数量(因为平行四边形被算了两次)。

算法分析

  • 核心思想: 通过计算线段的斜率和截距来统计平行线对,然后通过计算线段中点来消除平行四边形的重复计数。
  • 时间复杂度 :
    • 枚举所有线段并计算斜率、截距、中点:O(N^2),其中 N 是点的数量。
    • 统计平行线对和中点信息:O(N^2)。
    • 最后遍历 cntcnt2 字典:在最坏情况下,每个斜率或中点可能对应 O(N) 条线段,所以也是 O(N^2)。
    • 总时间复杂度为 O(N^2)。
  • 空间复杂度: O(N^2),主要用于存储斜率-截距和中点-斜率的字典。
  • 技巧 :
    • 使用 defaultdict 简化计数逻辑。
    • 处理浮点数精度问题:在比较斜率时,如果直接用浮点数可能导致精度问题。这里通过 dx, dy 的比值来表示斜率,并使用 inf 处理垂直线,可以避免部分精度问题。但在存储 b 值时,仍可能存在浮点数精度问题,实际实现中可能需要考虑将斜率和截距表示为分数形式或通过 gcd 简化。该方案中,由于截距的计算涉及到浮点数,因此在Python中直接使用浮点数作为字典键可能会有精度风险。
    • 将中点坐标表示为元组 (x+x2, y+y2) 作为字典键。

代码实现(Python)- 方案一:嵌套 defaultdict

python 复制代码
from collections import defaultdict
from math import inf
from typing import List


class Solution:
    def countTrapezoids(self, points: List[List[int]]) -> int:
        cnt = defaultdict(lambda:defaultdict(int))#斜率->截距->个数
        cnt2 = defaultdict(lambda:defaultdict(int)) #中点->斜率->个数

        for i,(x,y) in enumerate(points):
            for x2,y2 in points[:i]:
                dy = y - y2
                dx = x - x2
                k = dy/dx if dx != 0 else inf
                b = (y*dx - x*dy)/dx if dx != 0 else x # 截距
                cnt[k][b] += 1
                cnt2[(x+x2,y+y2)][k] += 1

        ans = 0
        # 统计所有平行线对形成的梯形 (包括平行四边形被计算两次)
        for m in cnt.values():
            s = 0 # 累计之前线段的数量
            for c in m.values():
                ans += c*s # 当前截距上的c条线段与之前s条线段组成梯形
                s += c

        # 减去平行四边形的重复计数
        for m in cnt2.values():
            s = 0 # 累计之前经过相同中点但不同斜率的线段数量
            for c in m.values():
                ans -= s*c # 当前斜率的c条线段与之前s条线段组成平行四边形
                s+=c

        return ans

代码实现(Python)- 方案二:使用 Counter

python 复制代码
from collections import defaultdict, Counter
from math import inf
from typing import List

class Solution:
    def countTrapezoids(self, points: List[List[int]]) -> int:
        groups = defaultdict(list)
        group2 = defaultdict(list)

        for i,(x,y) in enumerate(points):
            for x2,y2 in points[:i]:
                dy = y - y2
                dx = x - x2
                k = dy/dx if dx != 0 else inf
                b = (y*dx - x*dy)/dx if dx != 0 else x
                groups[k].append(b)
                group2[(x+x2,y+y2)].append(k)

        ans = 0
        for g in groups.values():
            if len(g) == 1:
                continue
            s = 0
            for c in Counter(g).values():
                ans += c*s
                s += c

        for g in group2.values():
            if len(g) == 1:
                continue
            s = 0
            for c in Counter(g).values():
                ans -= s*c
                s += c

        return ans

总结与反思

  1. 几何问题转换为代数计数: 将二维几何图形的计数问题巧妙地转化为线段斜率、截距和中点的计数问题。
  2. 避免浮点数精度问题 : 优先使用整数运算或分数表示来处理斜率和截距,以避免浮点数比较的精度问题。虽然示例代码直接使用了浮点数,但在实际竞赛中,通常建议将斜率表示为 (dy, dx) 的元组,并通过 gcd 简化,或者将截距也进行类似处理。
  3. 容斥原理: 通过"统计所有平行线对形成的梯形(包括重复计算的平行四边形)"再"减去平行四边形的重复计数"来得到最终结果,这是容斥原理的体现。
  4. 中点性质: 平行四边形的对角线互相平分,即中点重合。利用这个性质来识别平行四边形是关键。
  5. Python defaultdictCounter 的妙用 : 两种方案都展示了 Python collections 模块的强大功能。方案一使用嵌套的 defaultdict 直接进行计数,逻辑清晰;方案二先收集所有截距/斜率,再用 Counter 统一计数,是另一种有效的思路。
相关推荐
航Hang*1 天前
Photoshop 图形与图像处理技术——第8章:图像的色彩与色彩调整和图像的输出与优化
图像处理·笔记·ui·photoshop
小桥流水---人工智能1 天前
风电机组故障诊断与状态监测方法的研究局限性整理(背景笔记)
笔记
POLITE31 天前
Leetcode 41.缺失的第一个正数 JavaScript (Day 7)
javascript·算法·leetcode
菩提小狗1 天前
小迪安全笔记_第4天|扩展&整理|30+种加密编码进制全解析:特点、用处与实战识别指南|小迪安全笔记|网络安全|
笔记·安全·web安全
xian_wwq1 天前
【学习笔记】OSI安全架构体系
网络·笔记·学习
love530love1 天前
Windows 11 下再次成功本地编译 Flash-Attention 2.8.3 并生成自定义 Wheel(RTX 3090 sm_86 专属版)
人工智能·windows·笔记·编译·flash_attn·flash-attn·flash-attention
POLITE31 天前
Leetcode 42.接雨水 JavaScript (Day 3)
javascript·算法·leetcode
好易学·数据结构1 天前
可视化图解算法76:最大子数组和
数据结构·算法·leetcode·面试·动态规划·力扣·笔试
falldeep1 天前
Pandas入门指南
数据结构·算法·leetcode·pandas