Skip to content

Commit f3ee0f8

Browse files
committed
Add and update some graph notes and problem solving solutions
1 parent 69ec398 commit f3ee0f8

32 files changed

+879
-232
lines changed

leetcode/1249.minimum-remove-to-make-valid-parentheses.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## [1249. Minimum Remove to Make Valid Parentheses](https://leetcode.com/problems/minimum-remove-to-make-valid-parentheses/)
1+
# [1249. Minimum Remove to Make Valid Parentheses](https://leetcode.com/problems/minimum-remove-to-make-valid-parentheses/)
22

33
## Clarification Questions
44
* No, it's clear from problem description.
@@ -16,11 +16,12 @@ Input:
1616
Output:
1717
```
1818

19+
## Stack
1920
```kotlin
2021
fun minRemoveToMakeValid(s: String): String {
2122
if (s.isEmpty()) return ""
2223
val toRemoveIndexes = hashSetOf<Int>()
23-
// left parentheses index
24+
// index
2425
val stack = Stack<Int>()
2526
// Scan the string to find invalid right parentheses
2627
for (i in 0 until s.length) {
@@ -29,16 +30,14 @@ fun minRemoveToMakeValid(s: String): String {
2930
stack.push(i)
3031
} else if (c == ')') {
3132
if (stack.isEmpty()) {
32-
toRemoveIndexes.add(i)
33+
toRemoveIndexes.add(i) // invalid right parentheses
3334
} else {
3435
stack.pop()
3536
}
3637
}
3738
}
3839
// Invalid left parentheses
39-
while (!stack.isEmpty()) {
40-
toRemoveIndexes.add(stack.pop())
41-
}
40+
toRemoveIndexes.addAll(stack)
4241

4342
val answer = StringBuilder()
4443
for (i in 0 until s.length) {
@@ -50,9 +49,9 @@ fun minRemoveToMakeValid(s: String): String {
5049
```
5150

5251
* **Time Complexity**: `O(n)`, to iterate the string for scanning and building the result.
53-
* **Space Complexity**: `O(n)` for hash table and stack.
52+
* **Space Complexity**: `O(n)` for hash set and stack.
5453

55-
Another solution, we can iterate the string from left to right, to remove invalid right parentheses, then iterate from right to left to remove the invalid left parentheses.
54+
Another solution, we can iterate the string from left to right, to remove invalid left parentheses, then iterate from right to left to remove the invalid right parentheses.
5655

5756
```kotlin
5857
fun minRemoveToMakeValid(s: String): String {
@@ -61,9 +60,9 @@ fun minRemoveToMakeValid(s: String): String {
6160
for (i in 0 until s.length) {
6261
val c = s[i]
6362
if (c == '(') stack.push(i)
64-
else if (c == ')' && stack.isNotEmpty()) stack.pop()
63+
else if (c == ')' && stack.isNotEmpty()) stack.pop() // Remove the valid pairs
6564
}
66-
toRemove.addAll(stack)
65+
toRemove.addAll(stack) // The left parentheses that are not matched
6766
stack.clear()
6867
for (i in s.length - 1 downTo 0) {
6968
val c = s[i]

leetcode/1254.number-of-closed-islands.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,78 @@ Output:
1515
Input:
1616
Output:
1717
```
18+
## Traversal From Islands
19+
We can traverse all the islands, and check if current island is closed. We can use DFS or BFS to traverse the grid.
1820

19-
## Traversal
21+
```kotlin
22+
class Solution {
23+
private val directions = arrayOf(
24+
intArrayOf(-1, 0),
25+
intArrayOf(1, 0),
26+
intArrayOf(0, -1),
27+
intArrayOf(0, 1)
28+
)
29+
30+
private val visited = 2
31+
fun closedIsland(grid: Array<IntArray>): Int {
32+
val m = grid.size
33+
val n = grid[0].size
34+
var count = 0
35+
for (i in 0 until m) {
36+
for (j in 0 until n) {
37+
if (grid[i][j] == 0) {
38+
if (dfs(grid, i, j)) count++
39+
}
40+
}
41+
}
42+
return count
43+
}
44+
45+
private fun dfs(grid: Array<IntArray>, x: Int, y: Int): Boolean {
46+
val m = grid.size
47+
val n = grid[0].size
48+
if (x !in 0 until m || y !in 0 until n) return false
49+
if (grid[x][y] == 1) return true
50+
if (grid[x][y] == visited) return true
51+
52+
grid[x][y] = visited
53+
54+
var result = true
55+
directions.forEach { d ->
56+
// result = result & dfs(grid, x + d[0], y + d[1]) will not work, we have to traverse all the cells and then return the result.
57+
// but we can use `&&` to short-circuit the evaluation, it won't traverse all the cells of the same island.
58+
result = dfs(grid, x + d[0], y + d[1]) && result
59+
}
60+
return result
61+
}
62+
63+
private fun bfs(grid: Array<IntArray>, i: Int, j: Int): Boolean {
64+
val m = grid.size
65+
val n = grid[0].size
66+
val q = ArrayDeque<Pair<Int, Int>>()
67+
q.addLast(i to j)
68+
var closed = true
69+
while (q.isNotEmpty()) {
70+
val pair = q.removeFirst()
71+
val x = pair.first
72+
val y = pair.second
73+
74+
if (x !in 0 until m || y!in 0 until n) {
75+
closed = false
76+
continue
77+
}
78+
if (grid[x][y] != 0) continue
79+
grid[x][y] = visited
80+
directions.forEach { d ->
81+
q.addLast(x + d[0] to y + d[1])
82+
}
83+
}
84+
return closed
85+
}
86+
}
87+
```
88+
89+
## Traversal From Boundary
2090
To find the closed island, we can find non-closed islands first, then find the closed islands. How can we find the non-closed islands? We can start searching from the edges of the grid, the islands which are connected to the edges are non-closed islands. We can mark them as invalid during traversal. Then we can find the closed islands by traversing the grid again.
2191

2292
* We traversal (either DFS or BFS) from the 4 edges to search **non-closed** islands and mark the region as invalid during traversal.

leetcode/127.word-ladder.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,78 @@ log:[dog,lot,cog,]
2727
cog:[dog,log,]
2828
```
2929

30-
* Run BFS on the graph that calculates the shortest path.
30+
## BFS
31+
Run BFS on the graph that calculates the shortest path.
3132

33+
```kotlin
34+
class Solution {
35+
fun ladderLength(beginWord: String, endWord: String, wordList: List<String>): Int {
36+
val dict = HashSet<String>(wordList)
37+
val queue = ArrayDeque<String>()
38+
val visited = HashSet<String>()
39+
queue.addLast(beginWord)
40+
visited.add(beginWord) // Remember to add the beginWord to visited, otherwise it will be added to the queue again if it's in the wordList.
41+
42+
var steps = 0
43+
while (queue.isNotEmpty()) {
44+
val size = queue.size
45+
for (i in 0 until size) {
46+
val word = queue.removeFirst()
47+
if (word == endWord) return steps + 1
48+
val adjSet = getAdjacentStrings(word, dict)
49+
adjSet.forEach { adj ->
50+
if (!visited.contains(adj)) {
51+
queue.addLast(adj)
52+
visited.add(adj)
53+
}
54+
}
55+
}
56+
steps++
57+
}
58+
return 0
59+
}
60+
61+
// Or equivalently, we check inf the node which popped from the queue.
62+
queue.addLast(beginWord)
63+
// visited.add(beginWord) // We don't add to visited here.
64+
65+
while (queue.isNotEmpty()) {
66+
val size = queue.size
67+
for (i in 0 until size) {
68+
val word = queue.removeFirst()
69+
if (visited.contains(word)) continue // We check here.
70+
if (word == endWord) return steps + 1
71+
72+
visited.add(word)
73+
val adjSet = getAdjacentStrings(word, dict)
74+
adjSet.forEach { adj ->
75+
queue.addLast(adj)
76+
}
77+
}
78+
steps++
79+
}
80+
81+
private fun getAdjacentStrings(str: String, dict: HashSet<String>): HashSet<String> {
82+
val set = HashSet<String>()
83+
for (s in dict) {
84+
if (getDiff(str, s) == 1) set.add(s)
85+
}
86+
return set
87+
}
88+
89+
private fun getDiff(s1: String, s2: String): Int {
90+
val size = minOf(s1.length, s2.length)
91+
var diff = 0
92+
for (i in 0 until size) {
93+
if (s1[i] != s2[i]) diff++
94+
}
95+
return diff + abs(s1.length - s2.length)
96+
}
97+
}
98+
```
99+
100+
101+
> Deprecated: It's correct but outdated. The above solution is more concise and efficient.
32102
```kotlin
33103
fun ladderLength(beginWord: String, endWord: String, wordList: List<String>): Int {
34104
val visited = BooleanArray(wordList.size)

leetcode/130.surrounded-regions.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,18 @@ class Solution {
5757
val left = dfs(board, x, y - 1, regions)
5858
val right = dfs(board, x, y + 1, regions)
5959
return up && down && left && right
60+
61+
var result = true
62+
directions.forEach { adj ->
63+
result = result && dfs(board, x + adj[0], y + adj[1], regions)
64+
}
65+
return result
6066
}
6167
}
6268
```
6369

64-
### Bounary DFS
65-
The second approach is the reverse of the first approach. We need to find "non-closed" region first. We start from four boundaries, and find the regions we don't capture, and iterate the whole board again to capture the closed regions.
70+
### Bounary Traversal
71+
The second approach is the reverse traversal of the first approach. We need to find "non-closed" region first. We start from four boundaries, and find the regions we don't capture, and iterate the whole board again to capture the closed regions.
6672

6773
```kotlin
6874
private val directions = arrayOf(

leetcode/133.clone-graph.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ private fun clone(node: Node?): Node? {
7171
* **Space Complexity**: `O(n)` for hash table and recursion stack.
7272

7373
## BFS + Hash Table
74-
In BFS, we still need old and new node mapping to prevent duplicate visited. We have to update the neighbors of current node when we traverse the neighbors of the old node.
74+
In BFS, we enqueue the neighbors to clone the neighbors of the current node, and also update the neighbors of the new node.
75+
76+
In this approach, we still need old and new node mapping to prevent duplicate visited. We have to update the neighbors of current node when we traverse the neighbors of the old node.
7577

7678
```kotlin
7779
fun bfs(node: Node?): Node? {
@@ -95,11 +97,13 @@ fun bfs(node: Node?): Node? {
9597
queue.addLast(adj)
9698
} else {
9799
// We have cloned the node, we just update the neighbors.
98-
oldNewMapping[old]!!.neighbors.add(oldNewMapping[adj]!!)
100+
oldNewMapping[old]!!.neighbors.add(oldNewMapping[adj])
99101
}
100102
}
101103
}
102104
}
103105
return newNode
104106
}
105-
```
107+
```
108+
* **Time Complexity**: `O(n)` for BFS traversal.
109+
* **Space Complexity**: `O(n)` for hash table and queue.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# [1367. Linked List in Binary Tree](https://leetcode.com/problems/linked-list-in-binary-tree/description/)
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+
* The linked list or tree is empty.
14+
* The linked list is longer than the tree.
15+
list: 1 -> 2 -> 3
16+
tree:
17+
```
18+
1
19+
/ \
20+
2 3
21+
```
22+
23+
* The linked list is shorter than the tree.
24+
list: 1
25+
tree:
26+
```
27+
1
28+
/ \
29+
2 3
30+
```
31+
32+
# Approach
33+
It's similar to [572. Subtree of Another Tree](https://leetcode.com/problems/subtree-of-another-tree/), but we need to check if the linked list is a "substring" of the tree, not a subtree. (All the node in linked list have to be in the tree, but not all tree nodes have to be in the linked list.)
34+
35+
```kotlin
36+
fun isSubPath(head: ListNode?, root: TreeNode?): Boolean {
37+
// Slight different between `contains()`, we have to decide base on the definition of this recursive function.
38+
if (head == null && root == null) return true // Both are empty, is a subpath.
39+
if (head == null || root == null) return false // One of them is empty, is not a subpath.
40+
41+
// Search the linked list starting from the root node.
42+
return contains(head, root) ||
43+
isSubPath(head, root.left) || isSubPath(head, root.right) // Or search the linked list in the left and right subtree.
44+
}
45+
46+
private fun contains(head: ListNode?, root: TreeNode?): Boolean {
47+
// Tree contains empty list or we reach the end of the list, then it's a subpath.
48+
if (head == null) return true
49+
// It means we have reached the end of the tree, but the linked list is not found.
50+
if (root == null) return false
51+
52+
// Keep searching the linked list in the tree.
53+
return head.`val` == root.`val` && (contains(head.next, root.left) || contains(head.next, root.right))
54+
}
55+
```
56+
57+
* **Time Complexity:** `O(N * M)`, where `N` is the number of nodes in the tree and `M` is the number of nodes in the linked list.
58+
* **Space Complexity:** `O(N + M)`, we have to travesal the tree `O(N)` and we check `contain()` which takes `O(M)` space in each tree node.

leetcode/150.evaluate-reverse-polish-notation.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33
## Stack
44
We always evaluate the result of the last two operands with the operator `A, B, opeator`, that is `A operator B`. We can use a stack to store the operands and evaluate the result.
55

6+
```js
7+
(A) (B) operator
8+
A = (a) (b) operator or `a` // A could be a number or a result
9+
B = (c) (d) operator or `b`
10+
11+
// Example 1
12+
(1 2 +) (2 3 *) -
13+
(3) (6) -
14+
3 - 6 = -3
15+
16+
// Example 2
17+
(3) (2 3 *) +
18+
3 + 6 = 9
19+
```
20+
621
```kotlin
722
fun evalRPN(tokens: Array<String>): Int {
823
val stack = Stack<String>()

0 commit comments

Comments
 (0)