diff --git a/Hard/124 Binary Tree Maximum Path Sum.md b/Hard/124 Binary Tree Maximum Path Sum.md new file mode 100644 index 0000000..6bac9d9 --- /dev/null +++ b/Hard/124 Binary Tree Maximum Path Sum.md @@ -0,0 +1,60 @@ +# 124. Binary Tree Maximum Path Sum + +## Intuition + +The maximum path sum in a binary tree can be found by considering all possible paths that pass through each node. For any given node, the maximum path sum could be: + +1. The node's value itself +2. The node's value plus the maximum path from its left subtree +3. The node's value plus the maximum path from its right subtree +4. The node's value plus both left and right maximum paths + +## Approach + +We use a post-order traversal approach to solve this problem: + +1. For each node, we recursively calculate the maximum path sum from its left and right subtrees +2. We update the global maximum by considering all possible combinations: + - The current node's value alone + - Current node + left path + - Current node + right path + - Current node + left path + right path +3. We return the maximum path sum that can be extended from the current node to its parent (which can only include at most one of its children) + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(h) + +## Keywords + +- Binary Tree +- Post-order Traversal +- Recursion + +## Code + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func maxPathSum(root *TreeNode) int { + ret := math.MinInt + var postOrder func(node *TreeNode) int + postOrder = func(node *TreeNode) int { + if node == nil { + return 0 + } + left, right := postOrder(node.Left), postOrder(node.Right) + ret = max(ret, left + right + node.Val, left + node.Val, right + node.Val, node.Val) + return max(left + node.Val, right + node.Val, node.Val) + } + ret = max(ret, postOrder(root)) + return ret +} +``` diff --git a/Hard/834 Sum of Distances in Tree.md b/Hard/834 Sum of Distances in Tree.md new file mode 100644 index 0000000..c4cef75 --- /dev/null +++ b/Hard/834 Sum of Distances in Tree.md @@ -0,0 +1,72 @@ +# 834. Sum of Distances in Tree + +## Intuition + +A key observation is that we can leverage the tree structure and parent-child relationships to compute these sums efficiently. Instead of calculating distances from scratch for each node, we can use the results from a parent node to derive the results for its children. + +## Approach + +The solution uses a two-pass DFS approach: + +1. First DFS (dfs1): + - Calculates the number of child nodes for each node + - Computes the initial sum of distances for the root node (0) + - This pass builds the foundation for the second pass +2. Second DFS (dfs2): + - Uses the results from the first pass to compute distances for all other nodes + - For each child node, the sum of distances can be derived from its parent's sum using the formula: + `ret[child] = ret[parent] + (n - 2 * childNodes[child])` + - This formula works because moving from parent to child: + - Increases distance by 1 for all nodes not in the child's subtree + - Decreases distance by 1 for all nodes in the child's subtree + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(n) + +## Keywords + +- DFS +- Distance Calculation + +## Code + +```go +func sumOfDistancesInTree(n int, edges [][]int) []int { + ret, childNodes, tree := make([]int, n), make([]int, n), make([][]int, n) + for _, edge := range edges { + a, b := edge[0], edge[1] + tree[a], tree[b] = append(tree[a], b), append(tree[b], a) + } + + var dfs1 func(cur, prev int) + dfs1 = func(cur, prev int) { + childNodes[cur] = 1 + for _, neighbor := range tree[cur] { + if neighbor == prev { + continue + } + dfs1(neighbor, cur) + childNodes[cur] += childNodes[neighbor] + ret[cur] += ret[neighbor] + childNodes[neighbor] + } + } + + var dfs2 func(cur, prev int) + dfs2 = func(cur, prev int) { + for _, neighbor := range tree[cur] { + if neighbor == prev { + continue + } + ret[neighbor] = ret[cur] + (n- 2 * childNodes[neighbor]) + dfs2(neighbor, cur) + } + } + + dfs1(0, -1) + dfs2(0, -1) + + return ret +} +``` diff --git a/Medium/236 Lowest Common Ancestor of a Binary Tree.md b/Medium/236 Lowest Common Ancestor of a Binary Tree.md new file mode 100644 index 0000000..057b591 --- /dev/null +++ b/Medium/236 Lowest Common Ancestor of a Binary Tree.md @@ -0,0 +1,66 @@ +# 236. Lowest Common Ancestor of a Binary Tree + +## Intuition + + We can use a post-order traversal approach where we first check the left and right subtrees, then process the current node. + +## Approach + +1. We use a recursive helper function `find` that takes the current node and the two target nodes p and q. +2. The base cases are: + - If the current node is nil, return nil + - If the current node is either p or q, return the current node +3. We recursively search in both left and right subtrees +4. The logic for determining the LCA is: + - If both left and right subtrees return nil, return nil (neither p nor q found) + - If only one subtree returns a non-nil value, return that value (one of p or q found) + - If both subtrees return non-nil values, the current node is the LCA + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(h) + +## Keywords + +- Binary Tree +- Recursion +- DFS +- Post-order Traversal +- Lowest Common Ancestor + +## Code + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func find(node, p, q *TreeNode) *TreeNode { + if node == nil { + return nil + } + if node == p || node == q { + return node + } + left := find(node.Left, p, q) + right := find(node.Right, p, q) + if left == nil && right == nil { + return nil + } + if left != nil && right == nil { + return left + } + if left == nil && right != nil { + return right + } + return node +} +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + return find(root, p, q) +} +``` diff --git a/README.md b/README.md index 23c8040..fa192e2 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ | 50 | Pow(x, n) | [go](/Medium/50%20Pow(x,%20n).md) | M | | 51 | N-Queens | [go](/Hard/51%20N-Queens.md) | H | | 105 | Construct Binary Tree from Preorder and Inorder Tranversal | [go](/Medium/105%20Construct%20Binary%20Tree%20from%20Preorder%20and%20Inorder%20Tranversal.md) | M | +| 124 | Binary Tree Maximum Path Sum | [go](Hard/124%20Binary%20Tree%20Maximum%20Path%20Sum.md) | H | | 135 | Candy | [go](/Hard/135%20Candy.md) | H | | 140 | Word Break II | [go](/Hard/140%20Word%20Break%20II.md) | H | | 146 | LRU Cache | [go](/Medium/146%20LRU%20Cache.md) | M | +| 236 | Lowest Common Ancestor of a Binary Tree | [go](/Medium/236%20Lowest%20Common%20Ancestor%20of%20a%20Binary%20Tree.md) | M | | 239 | Sliding Window Maximum | [go](/Hard/239%20Sliding%20Window%20Maximum.md) | H | | 322 | Coin Change | [go](/Medium/322%20Coin%20Change.md) | M | | 460 | LFU Cache | [go](/Hard/460%20LFU%20Cache.md) | H | @@ -29,6 +31,7 @@ | 757 | Set Intersection Size At Least Two | [go](/Hard/757%20Set%20Intersection%20Size%20At%20Least%20Two.md) | H | | 815 | Bus Routes | [go](/Hard/815%20Bus%20Routes.md) | H | | 765 | Couples Holding Hands | [go](/Hard/765%20Couples%20Holding%20Hands.md) | H | +| 834 | Sum of Distances in Tree | [go](/Hard/834%20Sum%20of%20Distances%20in%20Tree.md) | H | | 840 | Magic Squares In Grid | [go](/Medium/840%20Magic%20Squares%20In%20Grid.md) | M | | 881 | Boats to Save People | [go](/Medium/881%20Boats%20to%20Save%20People.md) | M | | 912 | Sort an Array | [go](/Medium/912%20Sort%20an%20Array.md) | M | diff --git a/Week8/124 AC.png b/Week8/124 AC.png new file mode 100644 index 0000000..376a3b1 Binary files /dev/null and b/Week8/124 AC.png differ diff --git a/Week8/124 Binary Tree Maximum Path Sum.md b/Week8/124 Binary Tree Maximum Path Sum.md new file mode 100644 index 0000000..6bac9d9 --- /dev/null +++ b/Week8/124 Binary Tree Maximum Path Sum.md @@ -0,0 +1,60 @@ +# 124. Binary Tree Maximum Path Sum + +## Intuition + +The maximum path sum in a binary tree can be found by considering all possible paths that pass through each node. For any given node, the maximum path sum could be: + +1. The node's value itself +2. The node's value plus the maximum path from its left subtree +3. The node's value plus the maximum path from its right subtree +4. The node's value plus both left and right maximum paths + +## Approach + +We use a post-order traversal approach to solve this problem: + +1. For each node, we recursively calculate the maximum path sum from its left and right subtrees +2. We update the global maximum by considering all possible combinations: + - The current node's value alone + - Current node + left path + - Current node + right path + - Current node + left path + right path +3. We return the maximum path sum that can be extended from the current node to its parent (which can only include at most one of its children) + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(h) + +## Keywords + +- Binary Tree +- Post-order Traversal +- Recursion + +## Code + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func maxPathSum(root *TreeNode) int { + ret := math.MinInt + var postOrder func(node *TreeNode) int + postOrder = func(node *TreeNode) int { + if node == nil { + return 0 + } + left, right := postOrder(node.Left), postOrder(node.Right) + ret = max(ret, left + right + node.Val, left + node.Val, right + node.Val, node.Val) + return max(left + node.Val, right + node.Val, node.Val) + } + ret = max(ret, postOrder(root)) + return ret +} +``` diff --git a/Week8/236 AC.png b/Week8/236 AC.png new file mode 100644 index 0000000..439cbe1 Binary files /dev/null and b/Week8/236 AC.png differ diff --git a/Week8/236 Lowest Common Ancestor of a Binary Tree.md b/Week8/236 Lowest Common Ancestor of a Binary Tree.md new file mode 100644 index 0000000..057b591 --- /dev/null +++ b/Week8/236 Lowest Common Ancestor of a Binary Tree.md @@ -0,0 +1,66 @@ +# 236. Lowest Common Ancestor of a Binary Tree + +## Intuition + + We can use a post-order traversal approach where we first check the left and right subtrees, then process the current node. + +## Approach + +1. We use a recursive helper function `find` that takes the current node and the two target nodes p and q. +2. The base cases are: + - If the current node is nil, return nil + - If the current node is either p or q, return the current node +3. We recursively search in both left and right subtrees +4. The logic for determining the LCA is: + - If both left and right subtrees return nil, return nil (neither p nor q found) + - If only one subtree returns a non-nil value, return that value (one of p or q found) + - If both subtrees return non-nil values, the current node is the LCA + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(h) + +## Keywords + +- Binary Tree +- Recursion +- DFS +- Post-order Traversal +- Lowest Common Ancestor + +## Code + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func find(node, p, q *TreeNode) *TreeNode { + if node == nil { + return nil + } + if node == p || node == q { + return node + } + left := find(node.Left, p, q) + right := find(node.Right, p, q) + if left == nil && right == nil { + return nil + } + if left != nil && right == nil { + return left + } + if left == nil && right != nil { + return right + } + return node +} +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + return find(root, p, q) +} +``` diff --git a/Week8/834 AC.png b/Week8/834 AC.png new file mode 100644 index 0000000..afc4021 Binary files /dev/null and b/Week8/834 AC.png differ diff --git a/Week8/834 Sum of Distances in Tree.md b/Week8/834 Sum of Distances in Tree.md new file mode 100644 index 0000000..c4cef75 --- /dev/null +++ b/Week8/834 Sum of Distances in Tree.md @@ -0,0 +1,72 @@ +# 834. Sum of Distances in Tree + +## Intuition + +A key observation is that we can leverage the tree structure and parent-child relationships to compute these sums efficiently. Instead of calculating distances from scratch for each node, we can use the results from a parent node to derive the results for its children. + +## Approach + +The solution uses a two-pass DFS approach: + +1. First DFS (dfs1): + - Calculates the number of child nodes for each node + - Computes the initial sum of distances for the root node (0) + - This pass builds the foundation for the second pass +2. Second DFS (dfs2): + - Uses the results from the first pass to compute distances for all other nodes + - For each child node, the sum of distances can be derived from its parent's sum using the formula: + `ret[child] = ret[parent] + (n - 2 * childNodes[child])` + - This formula works because moving from parent to child: + - Increases distance by 1 for all nodes not in the child's subtree + - Decreases distance by 1 for all nodes in the child's subtree + +## Complexity + +- Time complexity: O(n) +- Space complexity: O(n) + +## Keywords + +- DFS +- Distance Calculation + +## Code + +```go +func sumOfDistancesInTree(n int, edges [][]int) []int { + ret, childNodes, tree := make([]int, n), make([]int, n), make([][]int, n) + for _, edge := range edges { + a, b := edge[0], edge[1] + tree[a], tree[b] = append(tree[a], b), append(tree[b], a) + } + + var dfs1 func(cur, prev int) + dfs1 = func(cur, prev int) { + childNodes[cur] = 1 + for _, neighbor := range tree[cur] { + if neighbor == prev { + continue + } + dfs1(neighbor, cur) + childNodes[cur] += childNodes[neighbor] + ret[cur] += ret[neighbor] + childNodes[neighbor] + } + } + + var dfs2 func(cur, prev int) + dfs2 = func(cur, prev int) { + for _, neighbor := range tree[cur] { + if neighbor == prev { + continue + } + ret[neighbor] = ret[cur] + (n- 2 * childNodes[neighbor]) + dfs2(neighbor, cur) + } + } + + dfs1(0, -1) + dfs2(0, -1) + + return ret +} +```