Skip to content

Commit 8f447df

Browse files
committed
Add: Add 2025/11/2
1 parent 34a6542 commit 8f447df

File tree

3 files changed

+359
-0
lines changed

3 files changed

+359
-0
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# 2257. Count Unguarded Cells in the Grid
2+
3+
You are given two integers `m` and `n` representing a 0-indexed `m x n` grid.
4+
You are also given two 2D integer arrays `guards` and `walls` where `guards[i] = [row_i, col_i]` and `walls[j] = [row_j, col_j]` represent the positions of the $i^{th}$ guard and $j^{th}$ wall respectively.
5+
6+
A guard can see every cell in the four cardinal directions (north, east, south, or west) starting from their position unless obstructed by a wall or another guard.
7+
A cell is guarded if there is at least one guard that can see it.
8+
9+
Return the number of unoccupied cells that are not guarded.
10+
11+
**Constraints:**
12+
13+
- `1 <= m, n <= 10^5`
14+
- `2 <= m * n <= 10^5`
15+
- `1 <= guards.length, walls.length <= 5 * 10^4`
16+
- `2 <= guards.length + walls.length <= m * n`
17+
- `guards[i].length == walls[j].length == 2`
18+
- `0 <= row_i, row_j < m`
19+
- `0 <= col_i, col_j < n`
20+
- All the positions in `guards` and `walls` are unique.
21+
22+
## 基礎思路
23+
24+
本題要求在一個由牆壁 (`walls`) 與警衛 (`guards`) 組成的 $m \times n$ 網格中,計算「沒有被警衛看守、也沒有被佔據」的格子數量。警衛能沿著四個基本方向(上、下、左、右)直線觀察,直到被牆或另一名警衛阻擋。
25+
26+
在分析問題時,我們需要考慮以下幾個重點:
27+
28+
- **遮蔽條件**:警衛的視線會被「牆壁」或「其他警衛」阻擋。
29+
- **觀察方向**:警衛可以同時往四個方向延伸視線。
30+
- **可見格子標記**:若某格被至少一名警衛看到,就視為被看守。
31+
- **效率要求**:$m \times n \le 10^5$,必須避免二維 BFS 或全表掃描(會超時),需設計線性時間掃描法。
32+
33+
為此,我們採用以下策略:
34+
35+
- **一維壓平儲存結構**:將二維座標 $(r, c)$ 轉為一維索引 $r \times n + c$,使用 TypedArray (`Uint8Array`) 儲存每格狀態,節省記憶體。
36+
- **分方向線性掃描(Sweep Line)**
37+
- 先以**橫向掃描**處理左右兩方向的可視格;
38+
- 再以**縱向掃描**處理上下兩方向的可視格。
39+
每一行與每一列都僅被掃描兩次(正向與反向),確保 $O(mn)$ 時間內完成。
40+
- **狀態標記**
41+
- `0`:空格(可被看守);
42+
- `1`:牆壁(阻斷視線);
43+
- `2`:警衛;
44+
- `3`:被看守的空格。
45+
- **統計未看守格數**:起初總空格為 $m \times n -$(牆數 + 警衛數),最後扣除被看守格即可。
46+
47+
此設計可在不使用 BFS 的情況下高效地模擬警衛的直線視線,達成題目要求。
48+
49+
## 解題步驟
50+
51+
### Step 1:初始化常數與狀態陣列
52+
53+
建立四種狀態常數,並配置 `Uint8Array` 儲存每格狀態。
54+
55+
```typescript
56+
// 定義狀態常數:0=空格、1=牆、2=警衛、3=被看守
57+
const STATE_EMPTY = 0;
58+
const STATE_WALL = 1;
59+
const STATE_GUARD = 2;
60+
const STATE_GUARDED = 3;
61+
62+
// 以一維陣列表示 m × n 網格,節省記憶體開銷
63+
const totalCells = m * n;
64+
const state = new Uint8Array(totalCells);
65+
```
66+
67+
### Step 2:初始化未佔據格數
68+
69+
先計算所有非牆與非警衛格子的初始數量,方便最終扣除。
70+
71+
```typescript
72+
// 初始未佔據格數 = 總格數 - (牆 + 警衛)
73+
const wallsLength = walls.length;
74+
const guardsLength = guards.length;
75+
let unoccupiedCells = totalCells - wallsLength - guardsLength;
76+
```
77+
78+
### Step 3:標記牆壁位置
79+
80+
遍歷 `walls` 陣列,將對應格狀態設為 `STATE_WALL`
81+
82+
```typescript
83+
// 標記牆壁位置(視線阻斷)
84+
for (let i = 0; i < wallsLength; i += 1) {
85+
const rowIndex = walls[i][0];
86+
const columnIndex = walls[i][1];
87+
const index = rowIndex * n + columnIndex;
88+
state[index] = STATE_WALL;
89+
}
90+
```
91+
92+
### Step 4:標記警衛位置
93+
94+
遍歷 `guards` 陣列,將對應格狀態設為 `STATE_GUARD`
95+
96+
```typescript
97+
// 標記警衛位置(視線起點)
98+
for (let i = 0; i < guardsLength; i += 1) {
99+
const rowIndex = guards[i][0];
100+
const columnIndex = guards[i][1];
101+
const index = rowIndex * n + columnIndex;
102+
state[index] = STATE_GUARD;
103+
}
104+
```
105+
106+
### Step 5:橫向掃描(每行左→右與右→左)
107+
108+
對每一行進行兩次掃描:
109+
110+
- 第一次由左至右(模擬往右視線)
111+
- 第二次由右至左(模擬往左視線)
112+
113+
若視線活躍(前方有警衛未被牆阻斷),且格為空則標記為被看守。
114+
115+
```typescript
116+
// 被看守空格計數
117+
let guardedEmptyCount = 0;
118+
119+
// 逐行橫向掃描
120+
for (let rowIndex = 0; rowIndex < m; rowIndex += 1) {
121+
// ←→方向視線模擬
122+
let hasActiveGuard = false;
123+
let index = rowIndex * n;
124+
125+
// 左→右掃描
126+
for (let columnIndex = 0; columnIndex < n; columnIndex += 1) {
127+
const cell = state[index];
128+
129+
if (cell === STATE_WALL) {
130+
hasActiveGuard = false; // 牆阻擋視線
131+
} else if (cell === STATE_GUARD) {
132+
hasActiveGuard = true; // 新警衛開始發出視線
133+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
134+
// 標記被看守格
135+
state[index] = STATE_GUARDED;
136+
guardedEmptyCount += 1;
137+
}
138+
139+
index += 1;
140+
}
141+
142+
// 右→左掃描
143+
hasActiveGuard = false;
144+
index = rowIndex * n + (n - 1);
145+
146+
for (let columnIndex = n - 1; columnIndex >= 0; columnIndex -= 1) {
147+
const cell = state[index];
148+
149+
if (cell === STATE_WALL) {
150+
hasActiveGuard = false; // 牆阻斷反向視線
151+
} else if (cell === STATE_GUARD) {
152+
hasActiveGuard = true; // 警衛向左發出視線
153+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
154+
state[index] = STATE_GUARDED;
155+
guardedEmptyCount += 1;
156+
}
157+
158+
index -= 1;
159+
}
160+
}
161+
```
162+
163+
### Step 6:縱向掃描(每列上→下與下→上)
164+
165+
同理,再對每一列進行兩次掃描:
166+
167+
- 第一次由上至下(模擬往下視線)
168+
- 第二次由下至上(模擬往上視線)
169+
170+
```typescript
171+
// 逐列縱向掃描
172+
for (let columnIndex = 0; columnIndex < n; columnIndex += 1) {
173+
// 上→下掃描
174+
let hasActiveGuard = false;
175+
let index = columnIndex;
176+
177+
for (let rowIndex = 0; rowIndex < m; rowIndex += 1) {
178+
const cell = state[index];
179+
180+
if (cell === STATE_WALL) {
181+
hasActiveGuard = false; // 牆阻斷下視線
182+
} else if (cell === STATE_GUARD) {
183+
hasActiveGuard = true; // 警衛向下發出視線
184+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
185+
state[index] = STATE_GUARDED;
186+
guardedEmptyCount += 1;
187+
}
188+
189+
index += n;
190+
}
191+
192+
// 下→上掃描
193+
hasActiveGuard = false;
194+
index = (m - 1) * n + columnIndex;
195+
196+
for (let rowIndex = m - 1; rowIndex >= 0; rowIndex -= 1) {
197+
const cell = state[index];
198+
199+
if (cell === STATE_WALL) {
200+
hasActiveGuard = false; // 牆阻斷上視線
201+
} else if (cell === STATE_GUARD) {
202+
hasActiveGuard = true; // 警衛向上發出視線
203+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
204+
state[index] = STATE_GUARDED;
205+
guardedEmptyCount += 1;
206+
}
207+
208+
index -= n;
209+
}
210+
}
211+
```
212+
213+
### Step 7:計算結果並回傳
214+
215+
最後,用未佔據總格扣掉被看守格數,得到「未被看守的空格數」。
216+
217+
```typescript
218+
// 最終結果 = 總未佔據格數 - 被看守格數
219+
return unoccupiedCells - guardedEmptyCount;
220+
```
221+
222+
## 時間複雜度
223+
224+
- 初始化牆與警衛標記:$O(w + g)$,其中 $w$、$g$ 分別為牆與警衛數。
225+
- 四次方向掃描(兩行兩列):每格僅訪問常數次,為 $O(m \times n)$。
226+
- 總時間複雜度為 $O(m \times n + w + g)$。
227+
228+
> $O(m \times n + w + g)$
229+
230+
## 空間複雜度
231+
232+
- 使用一個大小為 $m \times n$ 的 `Uint8Array` 儲存狀態。
233+
- 額外僅有常數級變數,無遞迴或額外結構。
234+
- 總空間複雜度為 $O(m \times n)$。
235+
236+
> $O(m \times n)$
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
const STATE_EMPTY = 0;
2+
const STATE_WALL = 1;
3+
const STATE_GUARD = 2;
4+
const STATE_GUARDED = 3;
5+
6+
function countUnguarded(m: number, n: number, guards: number[][], walls: number[][]): number {
7+
// Allocate a compact typed array for all cell states to reduce memory overhead
8+
const totalCells = m * n;
9+
const state = new Uint8Array(totalCells);
10+
11+
// Compute how many cells are initially unoccupied (not wall or guard)
12+
const wallsLength = walls.length;
13+
const guardsLength = guards.length;
14+
let unoccupiedCells = totalCells - wallsLength - guardsLength;
15+
16+
// Mark all wall positions — these block line of sight
17+
for (let i = 0; i < wallsLength; i += 1) {
18+
const rowIndex = walls[i][0];
19+
const columnIndex = walls[i][1];
20+
const index = rowIndex * n + columnIndex;
21+
state[index] = STATE_WALL;
22+
}
23+
24+
// Mark all guard positions — these emit line of sight
25+
for (let i = 0; i < guardsLength; i += 1) {
26+
const rowIndex = guards[i][0];
27+
const columnIndex = guards[i][1];
28+
const index = rowIndex * n + columnIndex;
29+
state[index] = STATE_GUARD;
30+
}
31+
32+
// Track how many empty cells become guarded during sweeps
33+
let guardedEmptyCount = 0;
34+
35+
// Row Sweeps — Each row is processed twice to handle both left→right and right→left vision
36+
for (let rowIndex = 0; rowIndex < m; rowIndex += 1) {
37+
// Sweep left → right: simulate vision extending rightward
38+
let hasActiveGuard = false; // Whether a guard is currently "seeing" along this direction
39+
let index = rowIndex * n;
40+
41+
for (let columnIndex = 0; columnIndex < n; columnIndex += 1) {
42+
const cell = state[index];
43+
44+
if (cell === STATE_WALL) {
45+
hasActiveGuard = false; // Wall blocks the view — reset vision
46+
} else if (cell === STATE_GUARD) {
47+
hasActiveGuard = true; // New guard starts emitting vision
48+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
49+
// Mark visible empty cell only once
50+
state[index] = STATE_GUARDED;
51+
guardedEmptyCount += 1;
52+
}
53+
54+
index += 1;
55+
}
56+
57+
// Sweep right → left: simulate vision extending leftward
58+
hasActiveGuard = false;
59+
index = rowIndex * n + (n - 1);
60+
61+
for (let columnIndex = n - 1; columnIndex >= 0; columnIndex -= 1) {
62+
const cell = state[index];
63+
64+
if (cell === STATE_WALL) {
65+
hasActiveGuard = false; // Wall stops the backward vision
66+
} else if (cell === STATE_GUARD) {
67+
hasActiveGuard = true; // Guard now projects vision to the left
68+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
69+
state[index] = STATE_GUARDED;
70+
guardedEmptyCount += 1;
71+
}
72+
73+
index -= 1;
74+
}
75+
}
76+
77+
// Column Sweeps — Each column is processed twice for top→bottom and bottom→top vision
78+
for (let columnIndex = 0; columnIndex < n; columnIndex += 1) {
79+
// Sweep top → bottom: simulate downward vision
80+
let hasActiveGuard = false;
81+
let index = columnIndex;
82+
83+
for (let rowIndex = 0; rowIndex < m; rowIndex += 1) {
84+
const cell = state[index];
85+
86+
if (cell === STATE_WALL) {
87+
hasActiveGuard = false; // Wall interrupts downward vision
88+
} else if (cell === STATE_GUARD) {
89+
hasActiveGuard = true; // Guard starts projecting vision downward
90+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
91+
state[index] = STATE_GUARDED;
92+
guardedEmptyCount += 1;
93+
}
94+
95+
index += n;
96+
}
97+
98+
// Sweep bottom → top: simulate upward vision
99+
hasActiveGuard = false;
100+
index = (m - 1) * n + columnIndex;
101+
102+
for (let rowIndex = m - 1; rowIndex >= 0; rowIndex -= 1) {
103+
const cell = state[index];
104+
105+
if (cell === STATE_WALL) {
106+
hasActiveGuard = false; // Wall stops upward vision
107+
} else if (cell === STATE_GUARD) {
108+
hasActiveGuard = true; // Guard emits upward vision
109+
} else if (hasActiveGuard && cell === STATE_EMPTY) {
110+
state[index] = STATE_GUARDED;
111+
guardedEmptyCount += 1;
112+
}
113+
114+
index -= n;
115+
}
116+
}
117+
118+
// Compute unguarded result — subtract all guarded empties from total unoccupied
119+
return unoccupiedCells - guardedEmptyCount;
120+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function countUnguarded(m: number, n: number, guards: number[][], walls: number[][]): number {
2+
3+
}

0 commit comments

Comments
 (0)