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 统一计数,是另一种有效的思路。
相关推荐
zore_c1 小时前
【C语言】文件操作详解2(文件的顺序读写操作)
android·c语言·开发语言·数据结构·笔记·算法·缓存
d111111111d1 小时前
STM32低功耗学习-待机模式-(学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习
TL滕1 小时前
从0开始学算法——第七天(快速排序算法)【面试高频】
笔记·学习·算法·面试·职场和发展·排序算法
重生之我在番茄自学网安拯救世界1 小时前
网络安全中级阶段学习笔记(五):CSRF跨站请求伪造学习笔记(超全总结)
笔记·学习·网络安全·csrf·跨站请求伪造
浩瀚地学1 小时前
【Java】方法
java·开发语言·经验分享·笔记
('-')1 小时前
《从根上理解MySQL是怎样运行的》第二十三章笔记
数据库·笔记·mysql
丝斯20111 小时前
AI学习笔记整理(28)—— 计算机视觉之姿态估计与动作识别
人工智能·笔记·学习
博语小屋3 小时前
力扣 15.三数之和(medium)(双指针)
算法·leetcode·职场和发展
无敌最俊朗@3 小时前
双指针-力扣hot100-移动零.283
算法·leetcode·职场和发展