Skip to content

Commit b2a0708

Browse files
committed
Add: Add 2025/12/2
1 parent f54e3c9 commit b2a0708

File tree

3 files changed

+228
-0
lines changed

3 files changed

+228
-0
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# 3623. Count Number of Trapezoids I
2+
3+
You are given a 2D integer array `points`, where `points[i] = [x_i, y_i]` represents the coordinates of the $i^{th}$ point on the Cartesian plane.
4+
5+
A horizontal trapezoid is a convex quadrilateral with at least one pair of horizontal sides (i.e. parallel to the x-axis).
6+
Two lines are parallel if and only if they have the same slope.
7+
8+
Return the number of unique horizontal trapezoids that can be formed by choosing any four distinct points from `points`.
9+
10+
Since the answer may be very large, return it modulo `10^9 + 7`.
11+
12+
**Constraints:**
13+
14+
- `4 <= points.length <= 10^5`
15+
- `–10^8 <= x_i, y_i <= 10^8`
16+
- All points are pairwise distinct.
17+
18+
## 基礎思路
19+
20+
本題要求計算所有能由四個不同點組成的「水平梯形」數量。
21+
水平梯形的核心特徵為:至少要有一組上下底邊彼此平行且**水平**,也就是兩條邊必須位於不同的 y 座標上,並且各自由同一條水平線上的兩點形成。
22+
23+
可歸納以下核心觀察:
24+
25+
* **水平線上的兩點可形成一條水平線段**
26+
若在某個固定的 y 值上有 `c` 個點,則能形成水平線段的數量為 $\binom{c}{2}$。
27+
28+
* **一個水平梯形需要兩條位於不同 y 座標的水平線段**
29+
因此只要從所有水平線段中挑選任意兩條,且它們分屬不同的 y 座標,即構成一個合法的水平梯形。
30+
31+
* **直接兩兩枚舉會超時,必須以組合方式計算**
32+
若將所有水平線段總數記為 `S`,且每條 y 線上的線段數為 `s_i`
33+
則所有可挑兩條線段的方式為 $\binom{S}{2}$,
34+
但其中包含了挑選同一 y 座標下的兩條線段(無法構成梯形),需扣除 $\sum \binom{s_i}{2}$。
35+
36+
* **組合式展開後可化為前綴形式:**
37+
38+
$$
39+
\binom{S}{2} - \sum \binom{s_i}{2}
40+
= \frac{S^2 - \sum s_i^2}{2}
41+
$$
42+
43+
透過平方與平方和即可在不使用巢狀迴圈的情況下完成計算。
44+
45+
* **數值非常大,需使用 BigInt 並在過程中進行模運算**
46+
過程需保持所有運算於模空間內,以避免溢位與效能問題。
47+
48+
基於上述分析,可以採用以下策略:
49+
50+
* **以雜湊表統計每條水平線上的點數。**
51+
* **轉換每條水平線的點數為線段數,並累積線段總和與其平方總和。**
52+
* **透過組合式計算跨水平線的線段配對數量。**
53+
* **最後套用模運算與模反元素完成除以 2 的動作。**
54+
55+
此策略能在一次掃描與一次彙整後完成所有計算,適合處理最大 `10^5` 筆資料。
56+
57+
## 解題步驟
58+
59+
### Step 1:初始化模常數與點數統計表
60+
61+
建立必要的模常數與雜湊表,用於記錄每個 y 座標出現的點數。
62+
63+
```typescript
64+
// 使用 BigInt 以確保大型數字運算的安全性
65+
const modulus = 1000000007n;
66+
const modularInverseTwo = 500000004n; // 預先計算的 2 的模反元素
67+
68+
// 計算每條水平線上有多少個點(相同 y 值)
69+
const coordinateYToCountMap = new Map<number, number>();
70+
const numberOfPoints = points.length;
71+
72+
for (let index = 0; index < numberOfPoints; index += 1) {
73+
const coordinateY = points[index][1];
74+
const existingCount = coordinateYToCountMap.get(coordinateY) ?? 0;
75+
coordinateYToCountMap.set(coordinateY, existingCount + 1);
76+
}
77+
```
78+
79+
### Step 2:累積所有水平線段數與其平方總和
80+
81+
逐一檢查每條 y 水平線上的點數,若至少兩點,則可形成線段。
82+
我們累積線段總數與線段數平方的總和(後續用於組合式計算)。
83+
84+
```typescript
85+
// 這兩個變數分別儲存:
86+
// 1. 所有 y 線上的水平線段總數
87+
// 2. 所有水平線段數量的平方總和(用於避免巢狀迴圈)
88+
let sumOfSegmentsModulo = 0n;
89+
let sumOfSegmentSquaresModulo = 0n;
90+
91+
// 對每條水平線,計算其能產生多少線段
92+
for (const countOfPointsOnLine of coordinateYToCountMap.values()) {
93+
if (countOfPointsOnLine >= 2) {
94+
const countBig = BigInt(countOfPointsOnLine);
95+
96+
// 此水平線上所能形成的水平線段數
97+
const numberOfSegments = (countBig * (countBig - 1n)) / 2n;
98+
99+
const numberOfSegmentsModulo = numberOfSegments % modulus;
100+
101+
// 累積所有線段數
102+
sumOfSegmentsModulo =
103+
(sumOfSegmentsModulo + numberOfSegmentsModulo) % modulus;
104+
105+
// 累積線段數平方(未來用於扣除同線上的組合)
106+
sumOfSegmentSquaresModulo =
107+
(sumOfSegmentSquaresModulo +
108+
(numberOfSegmentsModulo * numberOfSegmentsModulo) % modulus) %
109+
modulus;
110+
}
111+
}
112+
```
113+
114+
### Step 3:計算跨水平線的線段配對總數
115+
116+
使用前述公式:
117+
118+
$$
119+
\frac{S^2 - \sum s_i^2}{2}
120+
$$
121+
122+
先將全部線段總數平方,再扣除同水平線內的線段配對。
123+
124+
```typescript
125+
// 將總線段數平方,用於組合計算
126+
const sumOfSegmentsSquaredModulo =
127+
(sumOfSegmentsModulo * sumOfSegmentsModulo) % modulus;
128+
129+
// 扣除所有「同一條水平線上的線段互配」之組合,使只剩跨線的情況
130+
let result =
131+
(sumOfSegmentsSquaredModulo -
132+
sumOfSegmentSquaresModulo +
133+
modulus) % modulus;
134+
```
135+
136+
### Step 4:透過模反元素完成除以 2,並回傳答案
137+
138+
套用除 2 的模運算,最後轉成一般數字回傳。
139+
140+
```typescript
141+
// 使用模反元素執行除以 2
142+
result = (result * modularInverseTwo) % modulus;
143+
144+
// 回傳最終答案(其值介於 0 至 1e9+7 之間,可安全轉 number)
145+
return Number(result);
146+
```
147+
148+
## 時間複雜度
149+
150+
- 需掃描所有點一次以統計 y 座標。
151+
- 需掃描所有不同 y 類別一次以計算組合資料。
152+
- 其餘皆為常數時間運算。
153+
- 總時間複雜度為 $O(n)$。
154+
155+
> $O(n)$
156+
157+
## 空間複雜度
158+
159+
- 需要一個雜湊表儲存所有 y 座標的點數。
160+
- 類別最多與點數同階,因此空間為線性。
161+
- 總空間複雜度為 $O(n)$。
162+
163+
> $O(n)$
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
function countTrapezoids(points: number[][]): number {
2+
// Using BigInt to ensure large-number arithmetic works safely
3+
const modulus = 1000000007n;
4+
const modularInverseTwo = 500000004n; // Precomputed inverse of 2 under modulo
5+
6+
// Count how many points exist on each horizontal line (same y value)
7+
const coordinateYToCountMap = new Map<number, number>();
8+
const numberOfPoints = points.length;
9+
10+
for (let index = 0; index < numberOfPoints; index += 1) {
11+
const coordinateY = points[index][1];
12+
const existingCount = coordinateYToCountMap.get(coordinateY) ?? 0;
13+
coordinateYToCountMap.set(coordinateY, existingCount + 1);
14+
}
15+
16+
// These store:
17+
// 1. How many horizontal segments exist across all y-lines
18+
// 2. And also their squared counts (for efficient pairing later)
19+
let sumOfSegmentsModulo = 0n;
20+
let sumOfSegmentSquaresModulo = 0n;
21+
22+
// For each horizontal line, compute how many pairs of points it has.
23+
// Each pair forms one horizontal segment.
24+
for (const countOfPointsOnLine of coordinateYToCountMap.values()) {
25+
if (countOfPointsOnLine >= 2) {
26+
const countBig = BigInt(countOfPointsOnLine);
27+
28+
// Number of horizontal segments on this line
29+
const numberOfSegments = (countBig * (countBig - 1n)) / 2n;
30+
31+
const numberOfSegmentsModulo = numberOfSegments % modulus;
32+
33+
// Add segment count to the total list
34+
sumOfSegmentsModulo =
35+
(sumOfSegmentsModulo + numberOfSegmentsModulo) % modulus;
36+
37+
// Add squared count (used later to avoid nested loops)
38+
sumOfSegmentSquaresModulo =
39+
(sumOfSegmentSquaresModulo +
40+
(numberOfSegmentsModulo * numberOfSegmentsModulo) % modulus) %
41+
modulus;
42+
}
43+
}
44+
45+
// We want to find how many ways we can pick two different horizontal lines
46+
// and take one segment from each. Squaring the total then subtracting the
47+
// self-combinations handles this in constant time.
48+
const sumOfSegmentsSquaredModulo =
49+
(sumOfSegmentsModulo * sumOfSegmentsModulo) % modulus;
50+
51+
// Remove "same line" combinations so only cross-line combinations remain
52+
let result =
53+
(sumOfSegmentsSquaredModulo -
54+
sumOfSegmentSquaresModulo +
55+
modulus) % modulus;
56+
57+
// Divide by 2 using modular inverse (avoids floating point issues)
58+
result = (result * modularInverseTwo) % modulus;
59+
60+
// Return final number as a normal JS number (safe since < 1e9+7)
61+
return Number(result);
62+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function countTrapezoids(points: number[][]): number {
2+
3+
}

0 commit comments

Comments
 (0)