Skip to content

Commit 7cd1a8a

Browse files
committed
Update tree problem notes
1 parent e55d623 commit 7cd1a8a

19 files changed

+554
-209
lines changed

leetcode/1110.delete-nodes-and-return-forest.md

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,38 +43,59 @@ Output:
4343
/ /
4444
2 6
4545
```
46+
* The root is deleted.
47+
* All the nodes are deleted.
48+
* All the nodes in subtrees are deleted.
49+
50+
## Breakdown
51+
> Given the values to delete, how to delete the nodes (prune all the subtrees) and return the root?
52+
```kotlin
53+
fun del(root: TreeNode?, toDelete: Set<Int>): TreeNode? {
54+
if (root == null) return null
55+
if (root.`val` in toDelete) {
56+
return null
57+
}
58+
root.left = del(root.left, toDelete)
59+
root.right = del(root.right, toDelete)
60+
return root
61+
}
62+
```
4663

4764
## Postorder
48-
**Idea!!** We can traverse the tree, if the current node is in the delete list, we can add its children to the forest.
65+
**Idea!!** We traverse each node as root, and check if we should delete the root. If we should delete the root, we add the children to the forest. But bfore that, we need to check all the children first.
4966

5067
```js
51-
1 del(1)
52-
/ \
53-
2 3
68+
root del(root)
69+
/ \
70+
left right
5471
... ...
5572

56-
// After delete `1`, the subtrees `2` and `3` become the forest.
57-
/ \
58-
2 3
73+
// After delete `root`, the subtrees `left` and `right` become the forest.
74+
/ \
75+
left right
5976
... ...
6077
```
61-
We use postorder to delete, because we need to delete the nodes from the bottom to the top. Here is one key point to note, if the original root is not in the delete list, we should add it to the forest first.
78+
We use postorder to delete, because we need to delete the nodes from the bottom to the top.
79+
80+
Here is one key point to note, if the original root is not in the delete list, we should add it to the forest first.
6281
```kotlin
6382
fun delNodes(root: TreeNode?, to_delete: IntArray): List<TreeNode?> {
64-
val set = HashSet<Int>()
65-
for (d in to_delete) set.add(d)
83+
val set = to_delete.toHashSet()
6684
val forest = mutableListOf<TreeNode?>()
67-
// If the root is not in the delete list, add it to the forest first.
68-
if (!set.contains(root?.`val`)) forest.add(root)
69-
delete(root, set, forest)
85+
val updatedRoot = delete(root, set, forest)
86+
// If the root is not deleted, we should add it to the forest. Otherwise, the root won't be added to the forest.
87+
if (updatedRoot != null) forest.add(updatedRoot)
7088
return forest
7189
}
7290

7391
private fun delete(root: TreeNode?, deleteSet: HashSet<Int>, forest: MutableList<TreeNode?>): TreeNode? {
7492
if (root == null) return null
7593

94+
// We delete the child nodes first.
7695
root.left = delete(root.left, deleteSet, forest)
7796
root.right = delete(root.right, deleteSet, forest)
97+
98+
// Then check if we should delete the current root, then all children become the forest.
7899
if (deleteSet.contains(root.`val`)) {
79100
if (root.left != null) forest.add(root.left)
80101
if (root.right != null) forest.add(root.right)

leetcode/114.flatten-binary-tree-to-linked-list.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Output:
7272
3
7373
```
7474

75-
## Preorder
75+
## Iterate and Relink
7676
We traverse the tree in preoder and store the nodes in a list. Then we relink the nodes in the list.
7777
```kotlin
7878
fun flatten(root: TreeNode?): Unit {
@@ -101,7 +101,7 @@ private fun preorder(root: TreeNode?, list: MutableList<TreeNode>) {
101101
* **Space Complexity**: `O(n)`.
102102

103103
## Preorder + Relink
104-
Use the template of preorder iterative traversal, and for every node, we relink the current node:
104+
We do a preorder traversal, and for every node, we relink the current node:
105105
1. We relink the right subtree after the right most node of left child.
106106
2. Move left child to right child.
107107
3. Clear left child.
@@ -169,6 +169,9 @@ fun flatten(root: TreeNode?): Unit {
169169
// Because we have relinked the left child to right child above: `current.right = left`
170170
// This move can ensure we travese all possible (original) left child first then right child.
171171
current = current.right
172+
173+
// This is wrong, because right is relinked to left child, we should move to the new right child.
174+
// current = right
172175
}
173176
}
174177

leetcode/129.sum-root-to-leaf-numbers.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ Output: 235 + 2371 + 24 = 2630
2727
## DFS
2828
For `2 -> 3 -> 5`, we traverse to build the number `235`:
2929
* Start from `0`.
30-
* At `2`, the number will become `0 * 10 + 2` -> `2`.
31-
* At `3`, the number will become `2 * 10 + 3` -> `23`.
32-
* At `5`, the number will become `23 * 10 + 5` -> `235`. And it's a leaf node, so we sum it.
30+
* At `2`, the number will become `0 * 10 + 2` -> `2`, then we go down to child node with `2`.
31+
* At `3`, the number will become `2 * 10 + 3` -> `23`, then we go down to child node with `23`.
32+
* At `5`, the number will become `23 * 10 + 5` -> `235`. And it's a leaf node, so we return `235`.
3333

3434
![](https://assets.leetcode-cn.com/solution-static/129/fig1.png)
3535
> Source: https://leetcode.cn/problems/sum-root-to-leaf-numbers/solutions/464666/qiu-gen-dao-xie-zi-jie-dian-shu-zi-zhi-he-by-leetc/
Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,133 @@
11
# [1367. Linked List in Binary Tree](https://leetcode.com/problems/linked-list-in-binary-tree/description/)
22

3-
## Clarification Questions
4-
* No, it's clear from problem description.
5-
63
## Test Cases
7-
### Normal Cases
8-
```
9-
Input:
10-
Output:
11-
```
124
### Edge / Corner Cases
135
* The linked list or tree is empty.
146
* The linked list is longer than the tree.
7+
```
158
list: 1 -> 2 -> 3
169
tree:
17-
```
1810
1
1911
/ \
2012
2 3
2113
```
2214

2315
* The linked list is shorter than the tree.
16+
```
2417
list: 1
2518
tree:
26-
```
2719
1
2820
/ \
2921
2 3
3022
```
3123

24+
* Tree is the subsequence of the linked list.
25+
```
26+
list: 5 -> 8
27+
tree:
28+
5
29+
/
30+
2
31+
/
32+
8
33+
```
34+
3235
# 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.)
36+
It's similar to [572. Subtree of Another Tree](../leetcode/572.subtree-of-another-tree.md), 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.)
3437

3538
```kotlin
3639
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-
40+
if (root == null) return false
41+
4142
// Search the linked list starting from the root node.
4243
return contains(head, root) ||
4344
isSubPath(head, root.left) || isSubPath(head, root.right) // Or search the linked list in the left and right subtree.
4445
}
4546

4647
private fun contains(head: ListNode?, root: TreeNode?): Boolean {
48+
if (head == null && root == null) return true
4749
// Tree contains empty list or we reach the end of the list, then it's a subpath.
48-
if (head == null) return true
50+
if (head == null && root != null) return true
4951
// It means we have reached the end of the tree, but the linked list is not found.
50-
if (root == null) return false
52+
if (head != null && root == null) return false
5153

5254
// Keep searching the linked list in the tree.
5355
return head.`val` == root.`val` && (contains(head.next, root.left) || contains(head.next, root.right))
5456
}
5557
```
5658

5759
* **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.
60+
* **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.
61+
62+
63+
## WA
64+
```js
65+
head = 1 -> 8
66+
root =
67+
1
68+
/
69+
6
70+
/
71+
8
72+
```
73+
```kotlin
74+
fun isSubPath(head: ListNode?, root: TreeNode?): Boolean {
75+
if (head == null && root == null) return true
76+
if (head == null && root != null) return true
77+
if (head != null && root == null) return false
78+
79+
val h = head!!
80+
val r = root!!
81+
val result1 =
82+
h.`val` == r.`val` && (
83+
isSubPath(h.next, r.left) ||
84+
isSubPath(h.next, r.right))
85+
86+
val result2 =
87+
isSubPath(h, r.left) ||
88+
isSubPath(h, r.right)
89+
90+
return result1 || result2
91+
}
92+
```
93+
94+
* `result1` represents the case that the linked list starts from the current node in the tree. And keep searching if all the nodes in the linked list are in the tree. It's **a continuation of the previous search**.
95+
* `result2` represents the case that we search the beginning of the linked list in the left and right subtree of the current node. It's **a new search**, not a continuation of the previous search.
96+
97+
The problem here is that `isSubPath()` will check current `root` and **also start a new search** in the left and right subtree. But we should not start a new search in `result1`, it should be a continuation of the previous search. We should create a new function to handle the continuation of the previous search.
98+
99+
Corrected version:
100+
```kotlin
101+
fun isSubPath(head: ListNode?, root: TreeNode?): Boolean {
102+
if (head == null && root == null) return true
103+
if (head == null && root != null) return true
104+
if (head != null && root == null) return false
105+
106+
val h = head!!
107+
val r = root!!
108+
// Check the current and continue the search from the previous search.
109+
val result1 =
110+
h.`val` == r.`val` && (
111+
search(h.next, r.left) || // Modified
112+
search(h.next, r.right)) // Modified
113+
114+
// Start a new search in the left and right subtree.
115+
val result2 =
116+
isSubPath(h, r.left) ||
117+
isSubPath(h, r.right)
118+
119+
return result1 || result2
120+
}
121+
122+
// It's a continuation of the previous search.
123+
private fun search(head: ListNode?, root: TreeNode?): Boolean {
124+
// Same as above.
125+
if (head == null && root == null) return true
126+
if (head == null && root != null) return true
127+
if (head != null && root == null) return false
128+
129+
val h = head!!
130+
val r = root!!
131+
return h.`val` == r.`val` && (search(head.next, root.left) || search(head.next, root.right))
132+
}
133+
```

leetcode/1372.longest-zigzag-path-in-a-binary-tree.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# [1372. Longest ZigZag Path in a Binary Tree](https://leetcode.com/problems/longest-zigzag-path-in-a-binary-tree/)
22

3+
## Breakdowns
4+
> 1. How to find the longest leftward or rightward path in a tree?
5+
6+
```kotlin
7+
private fun dfs(root: TreeNode?): Int {
8+
if (root == null) return 0
9+
return if (root.left != null) 1 + dfs(root.left) else 0
10+
}
11+
```
12+
313
## Double Recursion
414
```js
515
// Previous direction is right,
@@ -15,11 +25,13 @@ left \
1525
```
1626

1727
```kotlin
28+
private val left = 1
29+
private val right = -1
1830
private var longestPath = 0
1931
fun longestZigZag(root: TreeNode?): Int {
2032
if (root == null) return 0
21-
dfs(root.left, 0, 0)
22-
dfs(root.right, 1, 0)
33+
dfs(root.left, left, 0)
34+
dfs(root.right, right, 0)
2335
return longestPath
2436
}
2537

@@ -32,16 +44,16 @@ private fun dfs(root: TreeNode?, previousDirection: Int, steps: Int) {
3244
longestPath = maxOf(longestPath, steps + 1)
3345

3446
// Previously go left, then it should go right
35-
if (previousDirection == 0) {
36-
dfs(root.right, 1, steps + 1)
47+
if (previousDirection == left) {
48+
dfs(root.right, right, steps + 1)
3749

3850
// Or we start new zigzag path from left child
39-
dfs(root.left, 0, 0)
51+
dfs(root.left, left, 0)
4052
} else {
41-
dfs(root.left, 0, steps + 1)
53+
dfs(root.left, left, steps + 1)
4254

4355
// Or we start new zigzag path from right child
44-
dfs(root.right, 1, 0)
56+
dfs(root.right, right, 0)
4557
}
4658
}
4759
```

0 commit comments

Comments
 (0)