LeetCode 每日一题笔记 日期:2025.12.02 题目:3623. 统计梯形的数目 I

LeetCode 每日一题笔记

0. 前言

  • 日期:2025.12.02
  • 题目:3623. 统计梯形的数目 I
  • 难度:中等
  • 标签:数组 哈希表 组合数学

1. 题目理解

问题描述

给定二维整数数组 points(每个元素为平面上的点 [x_i, y_i]),统计可以从中选择四个不同点组成的水平梯形数量。水平梯形是至少有一对平行于 x 轴的边的凸四边形。结果需对 (10^9 + 7) 取余。

示例

输入:points = [[1,0],[2,0],[3,0],[2,2],[3,2]]

输出:3

解释:y=0的点选2个(3种组合),y=2的点选2个(1种组合),乘积为 (3 \times 1 = 3)。

2. 解题思路

核心观察

水平梯形的核心是选择两条不同的水平线(y坐标不同),并从每条线上各选2个点,这4个点即可构成梯形。因此问题转化为:

  1. 按 y 坐标分组,统计每个 y 对应的点数量;
  2. 计算每个 y 对应的"选2个点"的组合数 (C(n,2) = \frac{n(n-1)}{2});
  3. 计算所有不同 y 组合数的两两乘积和(即最终梯形数量)。

算法步骤

  1. 用哈希表统计每个 y 对应的点数量;
  2. 遍历哈希表,计算每个 y 的组合数,并维护"组合数总和"和"组合数平方和";
  3. 利用公式 (\frac{(\sum c_i)^2 - \sum c_i^2}{2}) 计算两两乘积和(避免双重循环);
  4. 对结果取模并处理负数、除法(用模逆元)。

3. 代码实现

java 复制代码
class Solution {
    private static final int MOD = 1000000007;

    public static int countTrapezoids(int[][] points) {
        long res = 0;
        Map<Integer, Integer> map = new HashMap<>();
        
        // 1. 统计每个y对应的点数量
        for (int[] p : points) {
            int y = p[1];
            map.put(y, map.getOrDefault(y, 0) + 1);
        }
        
        long sumC = 0;   // 组合数的总和
        long sumC2 = 0;  // 组合数的平方和
        
        // 2. 计算每个y的组合数,维护总和与平方和
        for (int cnt : map.values()) {
            long c = (long) cnt * (cnt - 1) / 2; // C(cnt,2)
            sumC = (sumC + c) % MOD;
            sumC2 = (sumC2 + c * c) % MOD;
        }
        
        // 3. 公式计算两两乘积和:(sumC² - sumC2)/2
        res = (sumC * sumC - sumC2) % MOD;
        res = (res + MOD) % MOD; // 处理负数
        res = res * 500000004 % MOD; // 乘以2的模逆元(替代除法)
        
        return (int) res;
    }
}

4. 代码优化说明

官方题解采用累加累乘的方式,避免公式推导和模逆元,逻辑更简洁:

java 复制代码
class Solution {
    public int countTrapezoids(int[][] points) {
        Map<Integer, Integer> pointNum = new HashMap<>();
        final int mod = 1000000007;
        long ans = 0, sum = 0;
        
        // 统计每个y的点数量
        for (int[] point : points) {
            pointNum.put(point[1], pointNum.getOrDefault(point[1], 0) + 1);
        }
        
        // 遍历每个y的组合数,累加两两乘积和
        for (int pNum : pointNum.values()) {
            long edge = (long) pNum * (pNum - 1) / 2;
            ans = (ans + edge * sum) % mod; // 当前组合数 × 已遍历组合数的总和
            sum = (sum + edge) % mod;       // 更新已遍历组合数的总和
        }
        
        return (int) ans;
    }
}

优化点:

  • 无需公式推导,通过动态维护 sum(已遍历组合数的总和),直接累加当前组合数与之前所有组合数的乘积;
  • 避免模逆元,仅用加法和乘法的模运算,逻辑更直观。

5. 复杂度分析

  • 时间复杂度:(O(n + k)),其中 (n) 是点的数量,(k) 是不同 y 的数量(遍历点和哈希表各一次);
  • 空间复杂度:(O(k)),哈希表存储不同 y 的点数量。

6. 总结

本题的核心是组合数学 + 哈希表分组,通过将问题转化为"不同 y 组合数的两两乘积和",利用数学公式或累加技巧避免双重循环,实现高效计算。官方题解的累加方式更简洁易写,而公式法更偏数学推导,两者时间复杂度一致。

相关推荐
锦瑟弦音3 小时前
微信小游戏分包(cocos自带分包)
笔记·游戏
LYFlied4 小时前
【每日算法】LeetCode 153. 寻找旋转排序数组中的最小值
数据结构·算法·leetcode·面试·职场和发展
唐装鼠4 小时前
rust自动调用Deref(deepseek)
开发语言·算法·rust
找方案4 小时前
我的 all-in-rag 学习笔记:文本分块 ——RAG 系统的 “信息切菜术“
人工智能·笔记·all-in-rag
HXR_plume4 小时前
【Web信息处理与应用课程笔记1】网页排序(上)
笔记
qcwl664 小时前
操作系统 真象还原 学习笔记#13
笔记·学习
m0_689618285 小时前
30 分钟打印!多材料3D打印软机器人内置驱动 + 自主避障
笔记·学习·机器人
ytttr8735 小时前
MATLAB基于LDA的人脸识别算法实现(ORL数据库)
数据库·算法·matlab
Rousson6 小时前
硬件学习笔记--93 静电防护方案(电阻、磁珠、电感、TVS等)
笔记·单片机·学习
思成不止于此6 小时前
【MySQL 零基础入门】事务精讲(二):ACID 特性与并发问题
数据库·笔记·学习·mysql