Skip to content

Commit 69ec398

Browse files
committed
Add and update some binary tree and BST notes and problem solving solutions
1 parent 12aeaf2 commit 69ec398

23 files changed

+551
-152
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# [1038. Binary Search Tree to Greater Sum Tree](https://leetcode.com/problems/binary-search-tree-to-greater-sum-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 greater value of the node comes from parent node. Or the smaller value of the node goes to parent node.
14+
```
15+
4(23)
16+
\
17+
6(14)
18+
/ \
19+
5(19) 8(8)
20+
21+
22+
4(4)
23+
/
24+
1(10)
25+
\
26+
2(9)
27+
\
28+
3(7)
29+
```
30+
31+
# Approach
32+
We only think about what to do in current root node, other nodes are handled by recursion. For the current root node:
33+
1. It will receive the sum of all nodes greater than itself. (The parameter of our recursive function)
34+
2. We convert the right child to a greater sum tree in bottom-up manner.
35+
3. After conver the right child, we will get the sum of all nodes greater than the right child. `rightSum`.
36+
4. We update the current root node value to `root.val + rightSum`.
37+
5. We convert the left child to a greater sum tree in top-down manner with `root.val + rightSum` as the parameter.
38+
6. Return the sum of root node and all its children as the next greater sum.
39+
40+
```kotlin
41+
fun bstToGst(root: TreeNode?): TreeNode? {
42+
if (root == null) return null
43+
build(root, 0)
44+
return root
45+
}
46+
47+
private fun build(root: TreeNode?, value: Int): Int {
48+
// If the root is null, return the value, not 0. It indicates that we
49+
// don't update the value of the current node.
50+
if (root == null) return value
51+
52+
// Build right child from bottom up approach
53+
// 4(4)
54+
// /
55+
// 1
56+
// \
57+
// 2
58+
// \
59+
// 3(7) We have to pass value 4 to node 3.
60+
var rightSum = build(root.right, value)
61+
62+
// We update from the result of right child
63+
var currentSum = root.`val` + rightSum
64+
root.`val` = currentSum
65+
66+
// Build left child from top down approach with new sum
67+
currentSum = build(root.left, currentSum)
68+
69+
// Why should we return currentSum? Because we need to return the sum of
70+
// the current node and all its children. Not just the current node.
71+
// For example, the tree below:
72+
// 4
73+
// \
74+
// 6(14) // here we should return 19 to the parent node (4)., not 14.
75+
// / \
76+
// 5(19) 8(8)
77+
//
78+
// Root 4 will receive 19, not 14. Because 21 is the sum of 5, 6, 8.
79+
return currentSum
80+
}
81+
82+
// Or equivalently, we can maintain a global variable to store the sum.
83+
private var sum = 0
84+
fun bstToGst(root: TreeNode?): TreeNode? {
85+
inorder(root)
86+
return root
87+
}
88+
89+
private fun inorder(root: TreeNode?) {
90+
if (root == null) return
91+
// We traverse the right subtree first, then the root, and then the left subtree.
92+
93+
inorder(root.right)
94+
sum += root.`val`
95+
root.`val` = sum
96+
inorder(root.left)
97+
}
98+
```
99+
100+
* **Time Complexity:** `O(n)`, where `n` is the number of nodes in the tree.
101+
* **Space Complexity:** `O(h)`

leetcode/104.maximum-depth-of-binary-tree.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@ fun maxDepth(root: TreeNode?): Int {
3434
// Empty tree
3535
if (root == null) return 0
3636

37-
// Leaf node
37+
// Leaf node (optional)
3838
if (root.left == null && root.right == null) return 1
3939

4040
// Node with one or two children
4141
return 1 + maxOf(maxDepth(root.left), maxDepth(root.right))
4242
}
4343
```
4444

45-
* **Time Complexity**: `O(n)`, iterate all nodes.
46-
* **Space Complextiy**: `O(h)`, height might be `lg n` (balanced) or `n` (skewed)
45+
* **Time Complexity**: `O(n)`, we traverse all nodes once.
46+
* **Space Complextiy**: `O(H)`, recursion need space for stack, and the space depends on the height of the tree. The height might be `lg n` (balanced) or `n` (skewed)
4747

4848
### BFS
4949
```kotlin
@@ -64,5 +64,5 @@ fun maxDepth(root: TreeNode?): Int {
6464
return depth
6565
}
6666
```
67-
* **Time Complexity**: `O(n)`, iterate all nodes.
68-
* **Space Complexity**: `O(n)`, iterate all nodes.
67+
* **Time Complexity**: `O(n)`, we traverse all nodes once.
68+
* **Space Complexity**: `O(n)`, we traverse all nodes in queue.

leetcode/105.construct-binary-tree-from-preorder-and-inorder-traversal.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,25 @@ Input:
1515
Output:
1616
```
1717

18+
## Recursive
1819
```js
19-
Preorder = [D L R]
20-
Inorder = [L D R]
20+
Preorder = [D L R]
21+
[D [X, X, X] [Y, Y, Y]]
22+
| [Left] [Right]
23+
|
24+
Root
25+
26+
Inorder = [L D R]
27+
[[X, X, X] D [Y, Y, Y]]
28+
[Left] | [Right]
29+
|
30+
Root
2131
```
2232

23-
You locate the `D` at first, then get the range of left subtree from inorder based on the `D` index, the remaining part is right subtree.
33+
1. You locate the `D` at first which is root from preorder.
34+
2. Then search for the `D` in inorder.
35+
3. The left part of `D` in inorder is left subtree.
36+
4. The right part of `D` in inorder is right subtree. Or you can get the size of left subtree from inorder, then total size subtracted by left subtree size and root is right subtree size.
2437

2538
```kotlin
2639
fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {
@@ -40,4 +53,7 @@ fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {
4053
root.right = buildTree(rightInPreorder, rightInInOrder)
4154
return root
4255
}
43-
```
56+
```
57+
58+
* **Time Complexity:** `O(n)` where `n` is the number of nodes in the tree.
59+
* **Space Complexity:** `O(n)` where `n` is the number of nodes in the tree.

leetcode/112.path-sum.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,36 @@ fun hasPathSum(root: TreeNode?, targetSum: Int): Boolean {
3838
return hasPathSum(root.left, targetSum - root.`val`) || hasPathSum(root.right, targetSum - root.`val`)
3939
}
4040
}
41+
42+
// Or equivalently, we can add up to target sum:
43+
fun hasPathSum(root: TreeNode?, targetSum: Int): Boolean {
44+
return pathSum(root, 0, targetSum)
45+
}
46+
47+
private fun pathSum(root: TreeNode?, sum: Int, target: Int): Boolean {
48+
if (root == null) return false
49+
if (root.left == null && root.right == null) {
50+
return sum + root.`val` == target
51+
}
52+
53+
return pathSum(root.left, sum + root.`val`, target) ||
54+
pathSum(root.right, sum + root.`val`, target)
55+
}
56+
57+
// root: The current node we are processing now
58+
// currentSum: The sum before processing the current node
59+
private fun dfs(root: TreeNode?, sum: Int) {
60+
if (root == null) return
61+
62+
// Process current node
63+
val currentSum = sum + root.`val`
64+
65+
// Do something with the current node
66+
67+
// Child nodes
68+
dfs(root.left, currentSum)
69+
dfs(root.right, currentSum)
70+
}
4171
```
4272

4373
* **Time Complexity**: `O(n)`.

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

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -147,38 +147,7 @@ private fun subtreeLast(root: TreeNode): TreeNode {
147147
* **Time Complexity**: `O(n)`, where `n` is the node of tree.
148148
* **Space Complexity**: `O(h)`, where `h` is the height of tree.
149149

150-
## Postorder (DFS)
151-
We can use postorder traversal to flatten the tree. The idea is to flatten the left subtree and right subtree first, then we relink the right subtree after the right most node of left subtree.
152-
153-
![](../media/114.flatten-binary-tree-to-linked-list.png)
154-
155-
```kotlin
156-
fun flatten(root: TreeNode?) {
157-
if (root == null) return
158-
159-
val left = root.left
160-
val right = root.right
161-
flatten(left)
162-
flatten(right)
163-
164-
// We relink onlyl when left child is not null,
165-
// otherwise, we will override right child with null.
166-
if (left != null) {
167-
root.right = left
168-
root.left = null
169-
170-
val rightOfLeft = subtreeLast(left)
171-
rightOfLeft?.right = right
172-
}
173-
}
174-
175-
private fun subtreeLast(node: TreeNode?): TreeNode? {
176-
return if (node?.right != null) subtreeLast(node.right!!)
177-
else node
178-
}
179-
```
180-
181-
## Postorder with Pointers (Space Optimization)
150+
## Preorder with Pointers (Space Optimization)
182151
Idea is the same, but we traversal with pointers only (removing stack or recursion).
183152

184153
```kotlin
@@ -210,7 +179,42 @@ private fun subtreeLast(root: TreeNode?): TreeNode? {
210179
}
211180
```
212181

213-
### Dry Run (Postorder with Pointers)
182+
* **Time Complexity**: `O(n)`, where `n` is the node of tree.
183+
* **Space Complexity**: `O(1)`.
184+
185+
## Postorder
186+
We can use postorder traversal to flatten the tree. The idea is to flatten the left subtree and right subtree first, then we relink the right subtree after the right most node of left subtree.
187+
188+
![](../media/114.flatten-binary-tree-to-linked-list.png)
189+
190+
```kotlin
191+
fun flatten(root: TreeNode?) {
192+
if (root == null) return
193+
194+
val left = root.left
195+
val right = root.right
196+
flatten(left)
197+
flatten(right)
198+
199+
// We relink onlyl when left child is not null,
200+
// otherwise, we will override right child with null.
201+
if (left != null) {
202+
root.right = left
203+
root.left = null
204+
205+
val rightOfLeft = subtreeLast(left)
206+
rightOfLeft?.right = right
207+
}
208+
}
209+
210+
private fun subtreeLast(node: TreeNode?): TreeNode? {
211+
return if (node?.right != null) subtreeLast(node.right!!)
212+
else node
213+
}
214+
```
215+
216+
217+
### Dry Run (Preorder with Pointers)
214218
```kotlin
215219
1
216220
/ \

leetcode/116.populating-next-right-pointers-in-each-node.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ fun connect(root: Node?): Node? {
4747

4848

4949
## Recursive
50+
We can start connecting two nodes, and for each two nodes, we can connect their children nodes.
51+
52+
> 传统的 traverse 函数是遍历二叉树的所有节点,但现在我们想遍历的其实是两个相邻节点之间的「空隙」。这样我们就可以通过这两个节点的 next 指针将它们连接起来。
53+
![](https://labuladong.online/algo/images/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%B3%BB%E5%88%97/3.png)
54+
>
55+
> Nice explanation & Source: https://labuladong.online/algo/data-structure/binary-tree-part1/#%E7%AC%AC%E4%BA%8C%E9%A2%98%E3%80%81%E5%A1%AB%E5%85%85%E8%8A%82%E7%82%B9%E7%9A%84%E5%8F%B3%E4%BE%A7%E6%8C%87%E9%92%88
5056
```kotlin
5157
/**
5258
* 1

leetcode/124.binary-tree-maximum-path-sum.md

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -60,41 +60,40 @@ Meanwhile, we can update the global maximum path sum, which will be one of the c
6060
2. Left + Root + Right
6161

6262
```kotlin
63-
class Solution {
64-
65-
private var result = Int.MIN_VALUE
66-
67-
fun maxPathSum(root: TreeNode?): Int {
68-
calculateMax(root)
69-
return result
70-
}
71-
72-
private fun calculateMax(root: TreeNode?): Int {
73-
if (root == null) return 0
74-
val left = calculateMax(root?.left)
75-
val right = calculateMax(root?.right)
76-
val current = root.`val`
77-
78-
// Global max should calcluate from
79-
// Root
80-
// / \
81-
// Left Right
82-
// Root
83-
// Root + Left
84-
// Root + Right
85-
// Root + Left + Right
86-
result = maxOf(result, value)
87-
result = maxOf(result, value + left)
88-
result = maxOf(result, value + right)
89-
result = maxOf(result, value + left + right)
90-
91-
// Current path sum should contains at least the value of current node, so it should be from
92-
// Root
93-
// Root + Left
94-
// Root + Right
95-
return maxOf(value, maxOf(value + left, value + right))
96-
}
97-
}
63+
private var globalMax = Int.MIN_VALUE
64+
65+
fun maxPathSum(root: TreeNode?): Int {
66+
pathSum(root)
67+
return globalMax
68+
}
69+
70+
private fun pathSum(root: TreeNode?): Int {
71+
if (root == null) return 0
72+
73+
val leftSum = pathSum(root.left)
74+
val rightSum = pathSum(root.right)
75+
76+
// Local max is the return value of this function, which includes
77+
// Root
78+
// Root + Left
79+
// Root + Right
80+
var localMax = root.`val`
81+
localMax = maxOf(localMax, root.`val` + leftSum)
82+
localMax = maxOf(localMax, root.`val` + rightSum)
83+
84+
// Global max should calcluate from
85+
// Root
86+
// / \
87+
// Left Right
88+
// Root (local max)
89+
// Root + Left (local max)
90+
// Root + Right (local max)
91+
// Root + Left + Right
92+
globalMax = maxOf(globalMax, localMax)
93+
globalMax = maxOf(globalMax, root.`val` + leftSum + rightSum)
94+
95+
return localMax
96+
}
9897
```
9998

10099
* **Time Complexity**: `O(n)`.

0 commit comments

Comments
 (0)