Skip to content

Commit caba0a4

Browse files
committed
Add solutions to new graph problems
1 parent 1e3f6c3 commit caba0a4

22 files changed

+1141
-201
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This project offers a curated collection of notes and resources covering fundame
2222
- [Binary Search Tree](./problems/bst-problems.md)
2323
- [Heap & Priority Queue](./topics/heap.md) ([Problems](./problems/heap-problems.md))
2424
- [Graph](./topics/graph.md) ([Problems](./problems/graph-problems.md))
25-
- [Shortest Path](./topics/shortest-path.md)
25+
- [Shortest Path](./topics/shortest-path.md) ([Problems](./problems/shortest-path-problems.md))
2626
- [Recursion](./topics/recursion.md)
2727
- [Dynamic Programming](./topics/dynamic-programming.md) ([Problems](./problems/dynamic-programming-problems.md))
2828
- [Greedy](./topics/greedy.md) ([Problems](./problems/greedy-problems.md))
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# [1042. Flower Planting With No Adjacent](https://leetcode.com/problems/flower-planting-with-no-adjacent/)
2+
3+
```kotlin
4+
fun gardenNoAdj(n: Int, paths: Array<IntArray>): IntArray {
5+
val graph = buildGraph(n, paths)
6+
val answer = IntArray(n)
7+
for (i in 0 until n) {
8+
if (answer[i] == 0) {
9+
dfs(graph, i, answer)
10+
}
11+
}
12+
return answer
13+
}
14+
15+
private fun dfs(graph: Array<HashSet<Int>>, i: Int, answer: IntArray) {
16+
if (answer[i] != 0) return
17+
18+
val types = (1..4).toHashSet()
19+
for (adj in graph[i]) {
20+
val adjType = answer[adj]
21+
types.remove(adjType)
22+
}
23+
answer[i] = types.first()
24+
graph[i].forEach { adj ->
25+
dfs(graph, adj, answer)
26+
}
27+
}
28+
29+
private fun buildGraph(n: Int, paths: Array<IntArray>): Array<HashSet<Int>> {
30+
val graph = Array(n) { HashSet<Int>() }
31+
for ((x, y) in paths) {
32+
// Minus 1 for 0-indexed
33+
graph[x - 1].add(y - 1)
34+
graph[y - 1].add(x - 1)
35+
}
36+
return graph
37+
}
38+
```

leetcode/1091.shortest-path-in-binary-matrix.md

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,45 @@
1-
## [1091. Shortest Path in Binary Matrix](https://leetcode.com/problems/shortest-path-in-binary-matrix/)
2-
3-
## Clarification Questions
4-
* No, it's clear from problem description.
5-
6-
## Test Cases
7-
### Normal Cases
8-
```
9-
Input:
10-
Output:
11-
```
12-
### Edge / Corner Cases
13-
*
14-
```
15-
Input:
16-
Output:
17-
```
1+
# [1091. Shortest Path in Binary Matrix](https://leetcode.com/problems/shortest-path-in-binary-matrix/)
182

3+
## BFS
194
```kotlin
205
private val directions = arrayOf(
21-
intArrayOf(-1, -1),
22-
intArrayOf(-1, 0),
23-
intArrayOf(-1, 1),
24-
intArrayOf(0, -1),
25-
intArrayOf(0, 1),
26-
intArrayOf(1, -1),
27-
intArrayOf(1, 0),
28-
intArrayOf(1, 1)
29-
)
6+
intArrayOf(-1, -1),
7+
intArrayOf(-1, 0),
8+
intArrayOf(-1, 1),
9+
intArrayOf(0, -1),
10+
intArrayOf(0, 1),
11+
intArrayOf(1, -1),
12+
intArrayOf(1, 0),
13+
intArrayOf(1, 1)
14+
)
3015

3116
fun shortestPathBinaryMatrix(grid: Array<IntArray>): Int {
3217
val n = grid.size
3318
if (grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return -1
34-
35-
val distances = Array(n) { _ -> IntArray(n) { _ -> Int.MAX_VALUE }}
19+
20+
var distance = 1
3621
val queue = ArrayDeque<Pair<Int, Int>>()
37-
queue.add(0 to 0)
38-
distances[0][0] = 1
39-
while (!queue.isEmpty()) {
40-
val node = queue.removeFirst()
41-
val x = node.first
42-
val y = node.second
43-
if (x == n - 1 && y == n - 1) return distances[x][y]
44-
directions.forEach { d ->
45-
val newX = x + d[0]
46-
val newY = y + d[1]
47-
val distance = distances[x][y] + 1
48-
if (newX in 0 until grid.size && newY in 0 until grid.size && grid[newX][newY] == 0) {
49-
if (distances[newX][newY] > distance) {
50-
distances[newX][newY] = distance
22+
val visited = HashSet<Pair<Int, Int>>()
23+
queue.addLast(0 to 0)
24+
visited.add(0 to 0)
25+
while (queue.isNotEmpty()) {
26+
val size = queue.size
27+
repeat (size) {
28+
val (x, y) = queue.removeFirst()
29+
if (x == n - 1 && y == n - 1) return distance
30+
for (d in directions) {
31+
val newX = x + d[0]
32+
val newY = y + d[1]
33+
if (newX !in 0 until n) continue
34+
if (newY !in 0 until n) continue
35+
if (visited.contains(newX to newY)) continue
36+
if (grid[newX][newY] == 0) {
5137
queue.addLast(newX to newY)
38+
visited.add(newX to newY)
5239
}
5340
}
5441
}
42+
distance++
5543
}
5644
return -1
5745
}

leetcode/1129.shortest-path-with-alternating-colors.md

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,45 @@
11
# [1129. Shortest Path with Alternating Colors](https://leetcode.com/problems/shortest-path-with-alternating-colors/)
22

3+
## Hints
4+
- What if you ignore the color alternation? How would you find the shortest path?
5+
- How can you represent the state so that you don't revisit the same node with the same previous color?
6+
- Why do you need to track the color of the previous edge in your BFS state?
7+
38
## Breakdowns
4-
> 1. How to calculate the shortest path from 0 to `i`?
9+
> 1. How to calculate the shortest path from `0` to `i`?
10+
11+
Start BFS from `0`, and maintain a distance array for each node.
512

6-
Start BFS from 0, and maintain the distance array.
13+
> 2. How to know the color of the edge for node `i`?
714
8-
> 2. How to know the color of edge of node `i`?
9-
Convert it to an adjacency list, so that we can get the color of the edge at `i`.
15+
Convert the edge lists to two adjacency lists: one for red edges, one for blue edges.
1016
```js
1117
var red[i] = set(...)
1218
var blue[i] = set(...)
1319
```
1420

1521
> 3. How to alternate color along the path?
1622
17-
We have to record the current color along th path.
18-
23+
Record the color of the previous edge in the BFS state, and only traverse edges of the opposite color next.
1924
```js
2025
node.color = !node.color
2126
```
2227

2328
> 4. How to know there is no such path?
2429
25-
Initialize the distance as `inf`, then it is impossible if it remains `inf` after traverse.
30+
Initialize the distance as `inf` (or `Int.MAX_VALUE`), then if it remains `inf` after traversal, it is unreachable.
31+
32+
## BFS with Color State
33+
- This is a classic BFS shortest path problem, but with an extra state: the color of the previous edge.
34+
- The key is to treat `(node, previous color)` as the BFS state, because you can revisit the same node if you arrive via a different color.
35+
- You need two visited arrays (or a 2D visited array): one for red, one for blue, to avoid revisiting the same node with the same color.
36+
- This pattern is similar to other BFS problems with extra state, such as [1293. Shortest Path in a Grid with Obstacles Elimination](1293.shortest-path-in-a-grid-with-obstacles-elimination.md), where the state includes remaining eliminations.
37+
- BFS guarantees the shortest path is found first for each state.
2638

27-
## BFS
2839
```kotlin
2940
data class MyNode(
3041
val index: Int,
31-
val isRed: Boolean,
42+
val isRed: Boolean, // true if the previous edge is red, false if blue
3243
val distance: Int
3344
)
3445

@@ -47,7 +58,7 @@ fun shortestAlternatingPaths(n: Int, redEdges: Array<IntArray>, blueEdges: Array
4758
val blueVisited = BooleanArray(n)
4859

4960
// Start from 0, and add the red and blue edges to the queue
50-
ueue.addLast(MyNode(0, true, 0))
61+
queue.addLast(MyNode(0, true, 0))
5162
queue.addLast(MyNode(0, false, 0))
5263
redVisited[0] = true
5364
blueVisited[0] = true
@@ -109,5 +120,27 @@ private fun convert(n: Int, edges: Array<IntArray>): Array<HashSet<Int>> {
109120
}
110121
```
111122

112-
* **Time Complexity:** `O(N + Red + Blue)`
113-
* **Space Complexity:** `O(N + Red + Blue)`
123+
- **Time Complexity**: `O(N + Red + Blue)`, where `N` is the number of nodes, and `Red`/`Blue` are the number of red/blue edges.
124+
- **Space Complexity**: `O(N + Red + Blue)` for adjacency lists and visited arrays.
125+
126+
## Edge Cases
127+
- If there are no outgoing edges from `0`, all other nodes are unreachable.
128+
- If a node can only be reached by two consecutive edges of the same color, it is unreachable.
129+
- Self-loops and parallel edges: the algorithm handles them naturally, as long as you track visited by color.
130+
- If the graph is disconnected, unreachable nodes should return `-1`.
131+
132+
## Pitfalls
133+
- Forgetting to track visited status separately for red and blue arrivals can lead to infinite loops or incorrect answers.
134+
- Not alternating colors correctly when traversing edges.
135+
- Updating the distance for a node without considering the color state can cause wrong results.
136+
- **Mistake**: Using a single visited array for all states (should be per color).
137+
- **Mistake**: Not initializing both red and blue starts from node `0`.
138+
139+
## Similar or Follow-up Problems
140+
- [1293. Shortest Path in a Grid with Obstacles Elimination](1293.shortest-path-in-a-grid-with-obstacles-elimination.md)
141+
- [1091. Shortest Path in Binary Matrix](1091.shortest-path-in-binary-matrix.md)
142+
- [934. Shortest Bridge](934.shortest-bridge.md)
143+
- [127. Word Ladder](127.word-ladder.md)
144+
- [785. Is Graph Bipartite?](785.is-graph-bipartite.md)
145+
- [886. Possible Bipartition](886.possible-bipartition.md)
146+
- [3552. Grid Teleportation Traversal](3552.grid-teleportation-traversal.md)
Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
## [1162. As Far from Land as Possible](https://leetcode.com/problems/as-far-from-land-as-possible/)
1+
# [1162. As Far from Land as Possible](https://leetcode.com/problems/as-far-from-land-as-possible/)
22

3-
> TODO: Review the notes + implementations
3+
## Multi-source BFS
4+
We can enqueue all `1`s first, and then expand the level by level.
45

5-
### BFS
6-
The problem is looking for the farest water cell from the nearest land cell. We can start from the land cells and traverse the water cells to find the farest distance.
6+
> - 因为我们只要先把所有的陆地都入队,然后从各个陆地同时开始一层一层的向海洋扩散,那么最后扩散到的海洋就是最远的海洋!并且这个海洋肯定是被离他最近的陆地给扩散到的!
7+
> - 你可以想象成你从每个陆地上派了很多支船去踏上伟大航道,踏遍所有的海洋。每当船到了新的海洋,就会分裂成4条新的船,向新的未知海洋前进(访问过的海洋就不去了)。如果船到达了某个未访问过的海洋,那他们是第一个到这片海洋的。很明显,这么多船最后访问到的海洋,肯定是离陆地最远的海洋。
8+
> - 陆地不断长大,直到覆盖整个地图,计算扩大了多少轮,输出轮数即可
79
810
```kotlin
911
fun maxDistance(grid: Array<IntArray>): Int {
1012
val n = grid.size
1113
var maxDistance = 0
1214

13-
val distances = Array(n) { IntArray(n) { Int.MAX_VALUE } }
15+
// Approach 1: We need to maintain a distance matrix.
16+
val distances = Array(n) { IntArray(n) { Int.MAX_VALUE } }
17+
// Approach 2: We need to avoid duplicate visit.
18+
val visited = HashSet<Pair<Int, Int>>()
19+
1420
// Find the nearest land
1521
val queue = ArrayDeque<Pair<Int, Int>>()
1622
var hasWater = false
@@ -20,6 +26,7 @@ fun maxDistance(grid: Array<IntArray>): Int {
2026
if (grid[i][j] == 1) {
2127
queue.addLast(i to j)
2228
distances[i][j] = 0
29+
visited.add(i to j)
2330
hasLand = true
2431
} else {
2532
hasWater = true
@@ -28,28 +35,60 @@ fun maxDistance(grid: Array<IntArray>): Int {
2835
}
2936
if (!hasWater || !hasLand) return -1
3037

31-
while (queue.isNotEmpty()) {
32-
val position = queue.removeFirst()
33-
val x = position.first
34-
val y = position.second
35-
if (x < 0 || y < 0 || x >= n || y >= n) continue
38+
// To be continued...
39+
}
40+
```
41+
42+
- Approach 1: We use the same approach as [542. 01 Matrix](542.01-matrix.md), we can maintain a distance matrix, and the maximum distance is the answer during the update of distance during the traversal.
43+
44+
```kotlin
45+
// Continue from the code above
46+
47+
while (queue.isNotEmpty()) {
48+
val position = queue.removeFirst()
49+
val x = position.first
50+
val y = position.second
3651

37-
directions.forEach { d ->
52+
for (d in directions) {
53+
val newX = x + d[0]
54+
val newY = y + d[1]
55+
if (newX !in 0 until n) continue
56+
if (newY !in 0 until n) continue
57+
if (distances[newX][newY] > distances[x][y] + 1) {
58+
distances[newX][newY] = distances[x][y] + 1
59+
queue.addLast(newX to newY)
60+
maxDistance = max(maxDistance, distances[newX][newY])
61+
}
62+
}
63+
}
64+
return maxDistance
65+
```
66+
67+
- Approach 2: We can use the same idea as [994. Rotting Oranges](994.rotting-orange.md) (multi-source BFS + level by level) to find the maximum distance, we enqueue all `1`s first, and then expand the level by level.
68+
69+
```kotlin
70+
// Continue from the code above
71+
while (queue.isNotEmpty()) {
72+
val size = queue.size
73+
repeat (size) {
74+
val (x, y) = queue.removeFirst()
75+
for (d in directions) {
3876
val newX = x + d[0]
3977
val newY = y + d[1]
40-
if (newX >= 0 && newY >= 0 && newX < n && newY < n) {
41-
val distance = abs(newX - x) + abs(newY - y)
42-
if (distances[newX][newY] > distances[x][y] + distance) {
43-
distances[newX][newY] = distances[x][y] + distance
44-
queue.addLast(newX to newY)
45-
46-
if (distances[newX][newY] != Int.MAX_VALUE) {
47-
maxDistance = max(maxDistance, distances[newX][newY])
48-
}
49-
}
78+
if (newX !in 0 until n) continue
79+
if (newY !in 0 until n) continue
80+
if (visited.contains(newX to newY)) continue
81+
if (grid[newX][newY] == 0) {
82+
queue.addLast(newX to newY)
83+
visited.add(newX to newY)
5084
}
5185
}
5286
}
53-
return maxDistance
87+
maxDistance++
5488
}
89+
/**
90+
We will enqueue the last level and increment the distance, so we need to subtract 1 from the result.
91+
As same as [994. Rotting Oranges](994.rotting-orange.md), we need to subtract 1 from the result.
92+
*/
93+
return maxDistance - 1
5594
```

0 commit comments

Comments
 (0)