Skip to content

Commit 147edcb

Browse files
committed
Add: Add 2025/11/20
1 parent eab643c commit 147edcb

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# 757. Set Intersection Size At Least Two
2+
3+
You are given a 2D integer array `intervals` where `intervals[i] = [start_i, end_i]` represents all the integers from `start_i` to `end_i` inclusively.
4+
5+
A containing set is an array `nums` where each interval from `intervals` has at least two integers in `nums`.
6+
7+
- For example, if `intervals = [[1,3], [3,7], [8,9]]`, then `[1,2,4,7,8,9]` and `[2,3,4,8,9]` are containing sets.
8+
9+
Return the minimum possible size of a containing set.
10+
11+
**Constraints:**
12+
13+
- `1 <= intervals.length <= 3000`
14+
- `intervals[i].length == 2`
15+
- `0 <= start_i < end_i <= 10^8`
16+
17+
## 基礎思路
18+
19+
本題要求構造一個整數集合,使得給定的每個區間都至少包含其中兩個數字,並希望集合的大小最小。
20+
21+
在觀察所有區間後,可以得到以下核心思維:
22+
23+
1. **每個區間都需要被至少兩個點覆蓋**
24+
因此若一個區間尚未被現有點覆蓋,必須新增足夠的點補上。
25+
26+
2. **儘可能將新增點放在越右側的位置**
27+
放置越靠右的點較容易同時落入未來區間,能減少之後的新增需求。
28+
29+
3. **適當排序能讓貪心決策具備全域最優性**
30+
若依照區間的右端點由小到大排序,則每次處理到的都是「最先到期」的區間,使得貪心選點策略能成立。
31+
若右端點相同,再依左端點由大到小處理,可避免提前選點造成後續覆蓋困難。
32+
33+
4. **每個區間根據已被覆蓋的點數分成三類情況**
34+
35+
* 沒有覆蓋 → 必須新增兩個點
36+
* 覆蓋一個 → 必須新增一個點
37+
* 覆蓋兩個以上 → 不需新增
38+
此策略搭配排序可以確保最少新增次數。
39+
40+
5. **最終的策略為典型的右端點貪心方法**
41+
透過排序與依序處理區間,加上將新增點放置於區間最右側位置,能保證各區間以最小集合大小被覆蓋。
42+
43+
## 解題步驟
44+
45+
### Step 1:排序所有區間
46+
47+
依右端點由小到大排序;若右端相同,則依左端點由大到小排序。
48+
此排序方式能讓貪心的覆蓋策略保持正確性。
49+
50+
```typescript
51+
const intervalsLength = intervals.length;
52+
53+
// 依 end 由小到大排序;若 end 相同則依 start 由大到小排序
54+
intervals.sort((firstInterval, secondInterval) => {
55+
const firstEnd = firstInterval[1];
56+
const secondEnd = secondInterval[1];
57+
58+
if (firstEnd === secondEnd) {
59+
// 相同 end 時,start 較大的排前
60+
return secondInterval[0] - firstInterval[0];
61+
}
62+
63+
return firstEnd - secondEnd;
64+
});
65+
```
66+
67+
### Step 2:初始化兩個追蹤點與計數器
68+
69+
準備追蹤目前已加入集合的最大值與第二大值,以辨識每個區間已被覆蓋多少點。
70+
71+
```typescript
72+
let minimumContainingSetSize = 0;
73+
74+
// lastPoint:目前已選點中最大者
75+
// secondLastPoint:目前已選點中第二大者
76+
let lastPoint = -1;
77+
let secondLastPoint = -1;
78+
```
79+
80+
### Step 3:遍歷各區間並依覆蓋狀態補點
81+
82+
依序處理排序後的區間,並根據已覆蓋的點數(0、1、或≥2)決定是否需要補點。
83+
84+
```typescript
85+
for (let index = 0; index < intervalsLength; index += 1) {
86+
const currentInterval = intervals[index];
87+
const intervalStart = currentInterval[0];
88+
const intervalEnd = currentInterval[1];
89+
90+
if (intervalStart > lastPoint) {
91+
// 區間中沒有任何既有點 → 補兩點(end-1 與 end)
92+
// 將點放越右越有利於覆蓋後續區間
93+
secondLastPoint = intervalEnd - 1;
94+
lastPoint = intervalEnd;
95+
minimumContainingSetSize += 2;
96+
} else if (intervalStart > secondLastPoint) {
97+
// 區間中僅有一個既有點 → 補一點 end
98+
secondLastPoint = lastPoint;
99+
lastPoint = intervalEnd;
100+
minimumContainingSetSize += 1;
101+
}
102+
// 若已有兩點落在區間內,則不需補點
103+
}
104+
```
105+
106+
### Step 4:回傳最終所需的最小集合大小
107+
108+
```typescript
109+
return minimumContainingSetSize;
110+
```
111+
112+
## 時間複雜度
113+
114+
- 排序所有區間:最壞情況為 $O(n^2)$(JavaScript V8 在自訂比較函式下可能退化),平均情況約為 $O(n \log n)$
115+
- 逐一處理區間:$O(n)$
116+
- 總時間複雜度為 $O(n^2)$
117+
118+
> $O(n^2)$
119+
120+
## 空間複雜度
121+
122+
- 僅使用常數額外變數
123+
- 總空間複雜度為 $O(1)$
124+
125+
> $O(1)$
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
function intersectionSizeTwo(intervals: number[][]): number {
2+
const intervalsLength = intervals.length;
3+
4+
// Sort by end ascending; if end is the same, sort by start descending
5+
intervals.sort((firstInterval, secondInterval) => {
6+
const firstEnd = firstInterval[1];
7+
const secondEnd = secondInterval[1];
8+
9+
if (firstEnd === secondEnd) {
10+
// For same end, place the interval with larger start first
11+
return secondInterval[0] - firstInterval[0];
12+
}
13+
14+
return firstEnd - secondEnd;
15+
});
16+
17+
let minimumContainingSetSize = 0;
18+
19+
// lastPoint: largest selected point so far
20+
// secondLastPoint: second largest selected point so far
21+
let lastPoint = -1;
22+
let secondLastPoint = -1;
23+
24+
for (let index = 0; index < intervalsLength; index += 1) {
25+
const currentInterval = intervals[index];
26+
const intervalStart = currentInterval[0];
27+
const intervalEnd = currentInterval[1];
28+
29+
if (intervalStart > lastPoint) {
30+
// No selected point falls inside this interval; need two new points.
31+
// Choose them as far to the right as possible (end - 1 and end)
32+
// to maximize coverage of upcoming intervals.
33+
secondLastPoint = intervalEnd - 1;
34+
lastPoint = intervalEnd;
35+
minimumContainingSetSize += 2;
36+
} else if (intervalStart > secondLastPoint) {
37+
// Exactly one selected point is inside this interval (lastPoint).
38+
// Add one more point at the rightmost position (end).
39+
secondLastPoint = lastPoint;
40+
lastPoint = intervalEnd;
41+
minimumContainingSetSize += 1;
42+
}
43+
// Else: interval already contains both secondLastPoint and lastPoint,
44+
// so it is already satisfied; no action required.
45+
}
46+
47+
return minimumContainingSetSize;
48+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function intersectionSizeTwo(intervals: number[][]): number {
2+
3+
}

0 commit comments

Comments
 (0)