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 统一计数,是另一种有效的思路。
相关推荐
夏鹏今天学习了吗7 小时前
【LeetCode热题100(92/100)】多数元素
算法·leetcode·职场和发展
Mixtral8 小时前
2026年4款学习转写工具测评:告别逐字整理,自动生成复习资料
笔记·学习·ai·语音转文字
鄭郑8 小时前
【playwright 学习笔记】原理讲解与基础操作 --- day01
笔记·学习
2301_810746318 小时前
CKA冲刺40天笔记 - day10 K8S namespace
笔记·容器·kubernetes·k8s
Yu_Lijing8 小时前
基于C++的《Head First设计模式》笔记——模式合作
c++·笔记·设计模式
代码游侠8 小时前
学习笔记——时钟系统与定时器
arm开发·笔记·单片机·嵌入式硬件·学习·架构
诸葛成9 小时前
渗透测试-信息收集
经验分享·笔记·课程设计
源代码•宸9 小时前
Leetcode—509. 斐波那契数【简单】
经验分享·算法·leetcode·面试·golang·记忆化搜索·动规
峰顶听歌的鲸鱼9 小时前
Kubernetes管理
运维·笔记·云原生·容器·kubernetes·云计算
踩坑记录10 小时前
leetcode hot100 206.反转链表 easy
leetcode