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 组合数的两两乘积和",利用数学公式或累加技巧避免双重循环,实现高效计算。官方题解的累加方式更简洁易写,而公式法更偏数学推导,两者时间复杂度一致。

相关推荐
宇来风满楼40 分钟前
U-KAN复现
人工智能·深度学习·神经网络·算法·机器学习
W_chuanqi1 小时前
单目标实数参数优化:算法jSO
算法
老鱼说AI1 小时前
算法初级教学第三步:链表
数据结构·算法·链表
CodeByV1 小时前
【算法题】双指针(一)
数据结构·算法
952361 小时前
二叉平衡树
java·数据结构·学习·算法
AIpanda8881 小时前
AI营销软件系统是什么?主要有哪些功能与优势?
算法
Rock_yzh1 小时前
LeetCode算法刷题——53. 最大子数组和
java·数据结构·c++·算法·leetcode·职场和发展·动态规划
阿_旭1 小时前
LAMP剪枝的基本原理与方法简介
算法·剪枝·lamp