Skip to content

Commit 2fee5d5

Browse files
committed
new(blogpost): lca
1 parent d18416e commit 2fee5d5

File tree

4 files changed

+284
-33
lines changed

4 files changed

+284
-33
lines changed
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
2+
# Understanding Trees, Binary Search Trees, and Finding the Lowest Common Ancestor
3+
4+
In the world of computer science, data structures are the building blocks of efficient algorithms. One of the most fundamental and versatile data structures is the **Tree**.
5+
6+
This post will take you on a journey from the basics of trees to a specific type called a Binary Search Tree (BST), explore common algorithms used with them, and finally, solve a classic problem: finding the Lowest Common Ancestor of two nodes in a BST.
7+
8+
## What is a Tree?
9+
10+
In computer science, a **Tree** is a hierarchical data structure that consists of nodes connected by edges.
11+
12+
Unlike linear data structures like arrays or linked lists, trees are non-linear and are used to represent hierarchical relationships.
13+
14+
### Key Terminology:
15+
16+
* **Node:** The fundamental part of a tree that stores data.
17+
* **Edge:** The connection between two nodes.
18+
* **Root:** The topmost node in a tree. It's the only node that doesn't have a parent.
19+
* **Parent:** A node that has a child node.
20+
* **Child:** A node that has a parent node.
21+
* **Leaf:** A node that does not have any children.
22+
* **Subtree:** A tree consisting of a node and its descendants.
23+
* **Depth:** The length of the path from the root to a specific node.
24+
* **Height:** The length of the longest path from a specific node to a leaf.
25+
26+
Trees are used in various applications, such as file systems, organization charts, and even in parsing expressions in compilers.
27+
28+
## Binary Search Trees (BSTs)
29+
30+
A **Binary Search Tree (BST)** is a special type of binary tree where the nodes are ordered in a specific way.
31+
32+
This ordering makes operations like searching, insertion, and deletion very efficient.
33+
34+
A binary tree is a BST if it satisfies the following properties:
35+
36+
1. The left subtree of a node contains only nodes with keys **lesser** than the node's key.
37+
2. The right subtree of a node contains only nodes with keys **greater** than the node's key.
38+
3. Both the left and right subtrees must also be binary search trees.
39+
40+
This structure ensures that for any given node, all the values in its left subtree are smaller, and all the values in its right subtree are larger.
41+
42+
## Common Tree Algorithms
43+
44+
Trees have a variety of algorithms for traversal and manipulation. The most common are traversal algorithms, which visit each node in the tree exactly once.
45+
46+
### Tree Traversal
47+
48+
There are two main approaches to traversing a tree:
49+
50+
1. **Depth-First Search (DFS):** This approach explores as far as possible down one branch before backtracking. There are three common ways to perform DFS:
51+
* **In-order Traversal:** Visit the left subtree, then the root, then the right subtree. For a BST, this traversal visits the nodes in ascending order.
52+
* **Pre-order Traversal:** Visit the root, then the left subtree, then the right subtree. This is useful for creating a copy of the tree.
53+
* **Post-order Traversal:** Visit the left subtree, then the right subtree, then the root. This is useful for deleting nodes from the tree.
54+
55+
2. **Breadth-First Search (BFS):** This approach explores the tree level by level. It visits all the nodes at a given depth before moving on to the next level. BFS is typically implemented using a queue.
56+
57+
## More Tree Algorithms in Go
58+
59+
Let's explore how to implement some of these fundamental tree algorithms in Go.
60+
61+
### Finding the Height/Depth of a Binary Tree
62+
63+
The **height** of a binary tree is the number of edges on the longest path from the root node to a leaf node. A tree with only a root node has a height of 0.
64+
65+
The concept is closely related to the **depth** of a node, which is its distance from the root. The height of the tree is, therefore, the maximum depth of any node in the tree.
66+
67+
We can calculate the height recursively. The height of a node is 1 plus the maximum height of its left or right subtree.
68+
69+
```go
70+
import "math"
71+
72+
// TreeNode definition from before
73+
type TreeNode struct {
74+
Val int
75+
Left *TreeNode
76+
Right *TreeNode
77+
}
78+
79+
func max(a, b int) int {
80+
if a > b {
81+
return a
82+
}
83+
return b
84+
}
85+
86+
func height(node *TreeNode) int {
87+
if node == nil {
88+
return -1 // Height of a null tree is -1
89+
}
90+
leftHeight := height(node.Left)
91+
rightHeight := height(node.Right)
92+
return 1 + max(leftHeight, rightHeight)
93+
}
94+
```
95+
96+
### DFS Traversals in Go
97+
98+
Here are the Go implementations for the three DFS traversal methods.
99+
100+
#### In-order Traversal
101+
102+
```go
103+
func inOrderTraversal(node *TreeNode) {
104+
if node == nil {
105+
return
106+
}
107+
inOrderTraversal(node.Left)
108+
fmt.Println(node.Val) // Process the node
109+
inOrderTraversal(node.Right)
110+
}
111+
```
112+
113+
#### Pre-order Traversal
114+
115+
```go
116+
func preOrderTraversal(node *TreeNode) {
117+
if node == nil {
118+
return
119+
}
120+
fmt.Println(node.Val) // Process the node
121+
preOrderTraversal(node.Left)
122+
preOrderTraversal(node.Right)
123+
}
124+
```
125+
126+
#### Post-order Traversal
127+
128+
```go
129+
func postOrderTraversal(node *TreeNode) {
130+
if node == nil {
131+
return
132+
}
133+
postOrderTraversal(node.Left)
134+
postOrderTraversal(node.Right)
135+
fmt.Println(node.Val) // Process the node
136+
}
137+
```
138+
139+
## LeetCode 235: Lowest Common Ancestor of a Binary Search Tree
140+
141+
Now, let's apply our knowledge to a classic problem.
142+
143+
The **Lowest Common Ancestor (LCA)** of two nodes, `p` and `q`, in a tree is the lowest (i.e., deepest) node that has both `p` and `q` as descendants.
144+
145+
### The Problem
146+
147+
Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST.
148+
149+
For example, consider the following BST:
150+
151+
```
152+
6
153+
/ \
154+
2 8
155+
/ \ / \
156+
0 4 7 9
157+
/ \
158+
3 5
159+
```
160+
161+
* The LCA of nodes `2` and `8` is `6`.
162+
* The LCA of nodes `2` and `4` is `2`, since a node can be a descendant of itself.
163+
* The LCA of nodes `3` and `5` is `4`.
164+
165+
166+
### The Solution
167+
168+
The properties of a BST make finding the LCA particularly efficient.
169+
170+
We can start at the root of the tree and use the values of `p` and `q` to decide where to go next.
171+
172+
Let's consider the current node we are at, let's call it `current`.
173+
174+
1. If both `p` and `q` are **greater** than `current.val`, it means that the LCA must be in the **right** subtree. So, we move to the right child.
175+
2. If both `p` and `q` are **less** than `current.val`, it means that the LCA must be in the **left** subtree. So, we move to the left child.
176+
3. If one of `p` or `q` is greater than `current.val` and the other is less than `current.val` (or if one of them is equal to `current.val`), then `current` is the LCA.
177+
178+
This is because `p` and `q` are on opposite sides of the current node, meaning it's the split point and thus the lowest common ancestor.
179+
180+
We can implement this logic both iteratively and recursively.
181+
182+
### Iterative Solution
183+
184+
```go
185+
/**
186+
* Definition for a binary tree node.
187+
* type TreeNode struct {
188+
* Val int
189+
* Left *TreeNode
190+
* Right *TreeNode
191+
* }
192+
*/
193+
194+
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
195+
current := root
196+
for current != nil {
197+
if p.Val > current.Val && q.Val > current.Val {
198+
current = current.Right
199+
} else if p.Val < current.Val && q.Val < current.Val {
200+
current = current.Left
201+
} else {
202+
return current
203+
}
204+
}
205+
return nil // Should not be reached in a valid BST
206+
}
207+
```
208+
209+
### Recursive Solution
210+
211+
```go
212+
/**
213+
* Definition for a binary tree node.
214+
* type TreeNode struct {
215+
* Val int
216+
* Left *TreeNode
217+
* Right *TreeNode
218+
* }
219+
*/
220+
221+
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
222+
if root == nil {
223+
return nil
224+
}
225+
226+
if p.Val > root.Val && q.Val > root.Val {
227+
return lowestCommonAncestor(root.Right, p, q)
228+
} else if p.Val < root.Val && q.Val < root.Val {
229+
return lowestCommonAncestor(root.Left, p, q)
230+
} else {
231+
return root
232+
}
233+
}
234+
```
235+
236+
Both of these solutions are very efficient, with a time complexity of O(H), where H is the height of the tree. In a balanced BST, this is O(log N), where N is the number of nodes.
237+
238+
The space complexity for the iterative solution is O(1), while the recursive solution has a space complexity of O(H) due to the call stack.
239+
240+
## Conclusion
241+
242+
Trees and Binary Search Trees are powerful data structures that are essential for any programmer's toolkit. By understanding their properties and the algorithms that operate on them, you can solve a wide range of problems efficiently.
243+
244+
The Lowest Common Ancestor problem is a perfect example of how the structure of a BST can be leveraged to find an elegant and optimal solution.

public/posts/posts.json

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,4 @@
11
[
2-
{
3-
"slug": "algos",
4-
"title": "Algorithms",
5-
"date": "2025-11-04",
6-
"description": "Algorithms",
7-
"tags": ["cs", "algorithms", "graphs"],
8-
"series": {
9-
"posts": [
10-
{
11-
"slug": "monotonic-stack",
12-
"title": "Monotonic Stack with Daily Temperatures",
13-
"date": "2025-11-05",
14-
"description": "Monotonic Stack with Daily Temperatures",
15-
"tags": ["cs", "algorithms", "stack", "monotonic-stack"],
16-
"category": "dev",
17-
"filename": "/algos/monotonic-stack.txt"
18-
},
19-
{
20-
"slug": "wquwpc",
21-
"title": "Weighted Quick-Union with Path Compression",
22-
"date": "2025-11-04",
23-
"description": "Weighted Quick-Union with Path Compression",
24-
"tags": ["cs", "algorithms", "graphs"],
25-
"category": "dev",
26-
"filename": "/algos/weighted-quick-union-with-path-compression.txt"
27-
}
28-
]
29-
}
30-
},
312
{
323
"slug": "ubuntu-once-more",
334
"title": "Ubuntu Once More",
@@ -243,5 +214,43 @@
243214
"tags": ["writing", "updates"],
244215
"category": "rant",
245216
"filename": "about-fezcodex.txt"
217+
},
218+
{
219+
"slug": "algos",
220+
"title": "Algorithms",
221+
"date": "2025-10-01",
222+
"description": "Algorithms",
223+
"tags": ["cs", "algorithms", "graphs"],
224+
"series": {
225+
"posts": [
226+
{
227+
"slug": "lca",
228+
"title": "Lowest Common Ancestor with Binary Search Tree",
229+
"date": "2025-11-07",
230+
"description": "Lowest Common Ancestor with Binary Search Tree",
231+
"tags": ["cs", "algorithms", "tree", "bst", "recursion", "iterative"],
232+
"category": "dev",
233+
"filename": "/algos/lowest-common-ancestor-of-a-binary-search-tree.txt"
234+
},
235+
{
236+
"slug": "monotonic-stack",
237+
"title": "Monotonic Stack with Daily Temperatures",
238+
"date": "2025-11-05",
239+
"description": "Monotonic Stack with Daily Temperatures",
240+
"tags": ["cs", "algorithms", "stack", "monotonic-stack"],
241+
"category": "dev",
242+
"filename": "/algos/monotonic-stack.txt"
243+
},
244+
{
245+
"slug": "wquwpc",
246+
"title": "Weighted Quick-Union with Path Compression",
247+
"date": "2025-11-04",
248+
"description": "Weighted Quick-Union with Path Compression",
249+
"tags": ["cs", "algorithms", "graphs"],
250+
"category": "dev",
251+
"filename": "/algos/weighted-quick-union-with-path-compression.txt"
252+
}
253+
]
254+
}
246255
}
247256
]

public/rss.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
<link>https://fezcode.com</link>
1010
</image>
1111
<generator>RSS for Node</generator>
12-
<lastBuildDate>Tue, 04 Nov 2025 22:07:04 GMT</lastBuildDate>
12+
<lastBuildDate>Thu, 06 Nov 2025 22:19:40 GMT</lastBuildDate>
1313
<atom:link href="https://fezcode.com/rss.xml" rel="self" type="application/rss+xml"/>
14-
<pubDate>Tue, 04 Nov 2025 22:07:04 GMT</pubDate>
14+
<pubDate>Thu, 06 Nov 2025 22:19:40 GMT</pubDate>
1515
<copyright><![CDATA[2025 Ahmed Samil Bulbul]]></copyright>
1616
<language><![CDATA[en]]></language>
1717
<managingEditor><![CDATA[samil.bulbul@gmail.com (Ahmed Samil Bulbul)]]></managingEditor>

scripts/generate-rss.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
const fs = require('fs');
32
const RSS = require('rss');
43
const path = require('path');
@@ -71,7 +70,6 @@ const generateRssFeed = () => {
7170
if (!fs.existsSync(postPath)) return; // Skip if file doesn't exist
7271

7372
const postContent = fs.readFileSync(postPath, 'utf-8');
74-
const postHtmlContent = marked(postContent); // Convert Markdown to HTML
7573
// The URL in the feed should be the canonical one, even if the site uses HashRouter
7674
const url = `https://fezcode.com/#/blog/${post.slug}`;
7775

0 commit comments

Comments
 (0)