Skip to content

Commit 8470187

Browse files
authored
Update articles (#5063)
1 parent a5ea5e4 commit 8470187

36 files changed

+3013
-1
lines changed

articles/add-two-numbers.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
## 1. Recursion
22

3+
### Intuition
4+
5+
We add the two linked lists exactly like adding two numbers on paper.
6+
7+
Each node contains one digit, and since the lists are stored in **reverse order**, the head contains the ones place — making addition easy.
8+
At every step:
9+
10+
1. Take a digit from `l1` (or 0 if it’s finished)
11+
2. Take a digit from `l2` (or 0 if it’s finished)
12+
3. Add them with the incoming `carry`
13+
4. Create a new node for the current digit (`sum % 10`)
14+
5. Pass the new `carry` (`sum // 10`) forward using **recursion**
15+
16+
The recursion naturally processes digits from left to right and stops only when:
17+
- both lists are fully processed **and**
18+
- no carry remains.
19+
20+
---
21+
22+
### Algorithm
23+
24+
1. Define a recursive function `add(l1, l2, carry)`:
25+
- If `l1`, `l2` are both `None` and `carry` is `0`, return `None`.
26+
- Extract:
27+
- `v1 = l1.val` if `l1` exists, else `0`
28+
- `v2 = l2.val` if `l2` exists, else `0`
29+
- Compute:
30+
- `total = v1 + v2 + carry`
31+
- `carry, digit = divmod(total, 10)`
32+
- Recursively compute the next node using:
33+
- `l1.next` if exists
34+
- `l2.next` if exists
35+
- updated `carry`
36+
- Return a node with value `digit` whose `next` is the recursive result.
37+
38+
2. In `addTwoNumbers`, call:
39+
- return add(l1, l2, 0)
40+
341
::tabs-start
442

543
```python
@@ -350,6 +388,48 @@ class Solution {
350388

351389
## 2. Iteration
352390

391+
### Intuition
392+
393+
We simulate normal addition the same way we do on paper — digit by digit.
394+
395+
The linked lists store numbers in **reverse order**, so the first nodes represent the 1’s place.
396+
This makes addition straightforward:
397+
398+
- Add the two digits.
399+
- Add the carry from the previous step.
400+
- Save the resulting digit (`sum % 10`) into a new node.
401+
- Update the carry (`sum // 10`).
402+
- Move both pointers forward.
403+
404+
We continue until **both lists are finished AND no carry remains**.
405+
A dummy node helps us easily build and return the final linked list.
406+
407+
---
408+
409+
### Algorithm
410+
411+
1. Create:
412+
- a `dummy` node (to build the answer)
413+
- a pointer `cur` pointing to `dummy`
414+
- an integer `carry = 0`
415+
416+
2. Loop while `l1` exists, `l2` exists, or `carry` is non-zero:
417+
- Read the current digit of each list (`0` if that list already ended)
418+
- Compute
419+
`sum = v1 + v2 + carry`
420+
- Update:
421+
`carry = sum // 10`
422+
`digit = sum % 10`
423+
- Append a new node containing `digit`
424+
- Move the pointers `l1`, `l2`, and `cur` forward
425+
426+
3. Return `dummy.next` (the head of the result list)
427+
428+
This ensures correct handling of:
429+
- different lengths of input lists
430+
- leftover carry
431+
- building the result in one pass
432+
353433
::tabs-start
354434

355435
```python

articles/balanced-binary-tree.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
## 1. Brute Force
22

3+
### Intuition
4+
A tree is balanced if **every node’s left and right subtree heights differ by at most 1**.
5+
6+
The brute-force approach directly follows the definition:
7+
- For every node, compute the height of its left subtree.
8+
- Compute the height of its right subtree.
9+
- Check if their difference is ≤ 1.
10+
- Recursively repeat this check for all nodes.
11+
12+
---
13+
14+
### Algorithm
15+
1. If the current node is `null`, the subtree is balanced.
16+
2. Compute:
17+
- `leftHeight = height(left subtree)`
18+
- `rightHeight = height(right subtree)`
19+
3. If `abs(leftHeight - rightHeight) > 1`, return `False`.
20+
4. Recursively check if:
21+
- left subtree is balanced
22+
- right subtree is balanced
23+
5. If all checks pass, return `True`.
24+
25+
Height function:
26+
- If node is null → return 0
27+
- Otherwise → `1 + max(height(left), height(right))`
28+
329
::tabs-start
430

531
```python
@@ -300,6 +326,30 @@ class Solution {
300326

301327
## 2. Depth First Search
302328

329+
### Intuition
330+
The brute-force solution wastes time by repeatedly recomputing subtree heights.
331+
We fix this by doing **one DFS that returns two things at once** for every node:
332+
333+
1. **Is the subtree balanced?** (`True`/`False`)
334+
2. **What is its height?**
335+
336+
This way, each subtree is processed only once.
337+
If at any node the height difference > 1, we mark it as unbalanced and stop worrying about deeper levels.
338+
339+
---
340+
341+
### Algorithm
342+
1. Write a DFS function that:
343+
- Returns `[isBalanced, height]`.
344+
2. For each node:
345+
- Recursively get results from left and right children.
346+
- A node is balanced if:
347+
- Left subtree is balanced
348+
- Right subtree is balanced
349+
- Height difference ≤ 1
350+
3. Height of the current node = `1 + max(leftHeight, rightHeight)`
351+
4. Run DFS on the root and return the `isBalanced` value.
352+
303353
::tabs-start
304354

305355
```python
@@ -604,6 +654,33 @@ class Solution {
604654

605655
## 3. Iterative DFS
606656

657+
### Intuition
658+
The recursive DFS solution computes height and balance in one postorder traversal.
659+
This iterative version does **the same thing**, but simulates recursion using a stack.
660+
661+
The idea:
662+
- We must visit each node **after** its children (postorder).
663+
- Once both children of a node are processed, we already know their heights.
664+
- Then we:
665+
1. Check if the height difference ≤ 1
666+
2. Save the node’s height (`1 + max(left, right)`)
667+
668+
If any node is unbalanced, return `False` immediately.
669+
670+
---
671+
672+
### Algorithm
673+
1. Use a stack to simulate postorder traversal.
674+
2. Use a dictionary/map (`depths`) to store the height of each visited node.
675+
3. For each node:
676+
- Traverse left until possible.
677+
- When left is done, try right.
678+
- When both children are done:
679+
- Get their heights from `depths`.
680+
- If the difference > 1 → tree is unbalanced → return `False`.
681+
- Compute current node height and store it.
682+
4. If the traversal completes without violations → return `True`.
683+
607684
::tabs-start
608685

609686
```python

articles/binary-search.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
## 1. Recursive Binary Search
22

3+
### Intuition
4+
5+
Binary search works by repeatedly cutting the search space in half.
6+
Instead of scanning the entire array, we check the **middle element**:
7+
8+
- If it’s the target → return the index.
9+
- If the target is larger → search only in the right half.
10+
- If the target is smaller → search only in the left half.
11+
12+
The recursive version simply expresses this idea as a function that keeps calling itself on the appropriate half until the target is found or the range becomes invalid.
13+
14+
### Algorithm
15+
16+
1. Define a recursive function that takes the current search range `[l, r]`.
17+
2. If `l > r`, the range is empty → return `-1`.
18+
3. Compute the middle index `m = (l + r) // 2`.
19+
4. Compare `nums[m]` with `target`:
20+
- If equal → return `m`.
21+
- If `nums[m] < target` → recursively search `[m + 1, r]`.
22+
- If `nums[m] > target` → recursively search `[l, m - 1]`.
23+
5. Start the recursion with the full range `[0, n - 1]`.
24+
6. Return the final result.
25+
326
::tabs-start
427

528
```python
@@ -173,6 +196,24 @@ class Solution {
173196

174197
## 2. Iterative Binary Search
175198

199+
### Intuition
200+
201+
Binary search checks the middle element of a sorted array and decides which half to discard.
202+
Instead of using recursion, the iterative approach keeps shrinking the search range using a loop.
203+
We adjust the left and right pointers until we either find the target or the pointers cross, meaning the target isn’t present.
204+
205+
### Algorithm
206+
207+
1. Initialize two pointers:
208+
- `l = 0` (start of array)
209+
- `r = len(nums) - 1` (end of array)
210+
2. While `l <= r`:
211+
- Compute `m = l + (r - l) // 2` (safe midpoint).
212+
- If `nums[m] == target`, return `m`.
213+
- If `nums[m] < target`, move search to the **right half**: update `l = m + 1`.
214+
- If `nums[m] > target`, move search to the **left half**: update `r = m - 1`.
215+
3. If the loop ends without finding the target, return `-1`.
216+
176217
::tabs-start
177218

178219
```python
@@ -352,6 +393,26 @@ class Solution {
352393

353394
## 3. Upper Bound
354395

396+
### Intuition
397+
398+
Upper bound binary search finds the **first index where a value greater than the target appears**.
399+
Once we know that position, the actual target—if it exists—must be right before it.
400+
So instead of directly searching for the target, we search for the boundary where values stop being ≤ target.
401+
Then we simply check whether the element just before that boundary is the target.
402+
403+
### Algorithm
404+
405+
1. Set `l = 0` and `r = len(nums)` (right is *one past* the last index).
406+
2. While `l < r`:
407+
- Compute midpoint `m`.
408+
- If `nums[m] > target`, shrink the right side → `r = m`.
409+
- Otherwise (`nums[m] <= target`), shrink the left side → `l = m + 1`.
410+
3. After the loop:
411+
- `l` is the **upper bound**: first index where `nums[l] > target`.
412+
- So the potential location of the target is `l - 1`.
413+
4. If `l > 0` and `nums[l - 1] == target`, return `l - 1`.
414+
5. Otherwise, return `-1` (target not found).
415+
355416
::tabs-start
356417

357418
```python
@@ -514,6 +575,28 @@ class Solution {
514575

515576
## 4. Lower Bound
516577

578+
### Intuition
579+
580+
Lower bound binary search finds the **first index where a value is greater than or equal to the target**.
581+
This means if the target exists in the array, this lower-bound index will point exactly to its first occurrence.
582+
So instead of directly searching for equality, we search for the **leftmost position** where the target *could* appear, then verify it.
583+
584+
This approach is especially useful for sorted arrays because it avoids overshooting and naturally handles duplicates.
585+
586+
### Algorithm
587+
588+
1. Initialize:
589+
- `l = 0`
590+
- `r = len(nums)` (right is one past the last index).
591+
2. While `l < r`:
592+
- Compute midpoint `m`.
593+
- If `nums[m] >= target`, shrink the search to the **left half**`r = m`.
594+
- Otherwise (`nums[m] < target`), search in the **right half**`l = m + 1`.
595+
3. After the loop:
596+
- `l` is the **lower bound**: first index where value ≥ target.
597+
4. If `l` is within bounds *and* `nums[l] == target`, return `l`.
598+
5. Otherwise, return `-1` (the target is not in the array).
599+
517600
::tabs-start
518601

519602
```python

0 commit comments

Comments
 (0)