questions B (backspace - burst-balloons)

This commit is contained in:
2025-05-24 22:06:49 +01:00
parent 0b83eff6f8
commit c4662f5001
67 changed files with 13945 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
title: Balance a Binary Search Tree
slug: balance-a-binary-search-tree
difficulty: medium
leetcode_id: 1382
leetcode_url: https://leetcode.com/problems/balance-a-binary-search-tree/
categories:
- trees
patterns:
- tree-traversal
- dfs
description: |
Given the `root` of a binary search tree, return *a **balanced** binary search tree with the same node values*. If there is more than one answer, return **any of them**.
A binary search tree is **balanced** if the depth of the two subtrees of every node never differs by more than `1`.
constraints: |
- The number of nodes in the tree is in the range `[1, 10^4]`
- `1 <= Node.val <= 10^5`
examples:
- input: "root = [1,null,2,null,3,null,4,null,null]"
output: "[2,1,3,null,null,null,4]"
explanation: "This is not the only correct answer, [3,1,4,null,2] is also correct."
- input: "root = [2,1,3]"
output: "[2,1,3]"
explanation: "The tree is already balanced, so the same structure is returned."
explanation:
intuition: |
Picture an unbalanced BST that's essentially become a linked list — each node only has a right child, forming a long chain. This degrades search operations from O(log n) to O(n).
The key insight is that a **BST's in-order traversal always produces a sorted array**. This property is fundamental: as you traverse left-root-right, you visit nodes in ascending order.
Think of it like this: if you have a sorted array and want to build the most balanced BST possible, you would naturally pick the **middle element** as the root. This ensures roughly half the elements go to the left subtree and half to the right — perfectly balanced!
By combining these two insights, the problem becomes straightforward:
1. Convert the BST to a sorted array (in-order traversal)
2. Build a balanced BST from the sorted array (recursive divide-and-conquer)
The beauty of this approach is that picking the middle element recursively guarantees the tree will be height-balanced.
approach: |
We solve this using **In-order Traversal + Divide-and-Conquer Reconstruction**:
**Step 1: Extract nodes via in-order traversal**
- Perform an in-order DFS traversal (left → root → right)
- Store each node's value in a list as we visit
- This produces a **sorted array** of all values
&nbsp;
**Step 2: Build balanced BST from sorted array**
- Use a recursive helper function that takes a range `[left, right]`
- Find the middle index: `mid = (left + right) // 2`
- Create a new node with the middle value — this becomes the subtree's root
- Recursively build the left subtree from `[left, mid - 1]`
- Recursively build the right subtree from `[mid + 1, right]`
&nbsp;
**Step 3: Handle base case**
- When `left > right`, the range is empty — return `None`
- This terminates the recursion at leaf boundaries
&nbsp;
**Step 4: Return the new root**
- The initial call with the full range `[0, n - 1]` returns the root of the balanced BST
&nbsp;
By always choosing the middle element, we ensure each subtree has at most half the remaining elements, guaranteeing the tree is balanced.
common_pitfalls:
- title: Modifying the Original Tree In-Place
description: |
Attempting to rebalance by rotating nodes in the original tree is complex and error-prone. While AVL or Red-Black tree rotations exist, they're overkill here.
The cleaner approach is to extract all values and rebuild from scratch — this is O(n) time and O(n) space regardless, which matches any rotation-based solution.
wrong_approach: "Complex tree rotations on the original structure"
correct_approach: "Extract values, rebuild from sorted array"
- title: Forgetting BST In-Order Property
description: |
If you try to extract values using pre-order or post-order traversal, you won't get a sorted array. Only **in-order traversal** (left → root → right) produces sorted output from a BST.
This property is essential — the reconstruction algorithm relies on the array being sorted to place elements correctly.
wrong_approach: "Using pre-order or BFS to extract values"
correct_approach: "In-order DFS traversal for sorted output"
- title: Off-By-One in Middle Calculation
description: |
When calculating the middle index, using `(left + right) // 2` works correctly. However, be careful with the recursive ranges:
- Left subtree: `[left, mid - 1]` — excludes the middle
- Right subtree: `[mid + 1, right]` — excludes the middle
Including the middle in either subtree would duplicate values.
wrong_approach: "Overlapping ranges that include mid twice"
correct_approach: "Exclusive ranges: [left, mid-1] and [mid+1, right]"
- title: Not Handling Empty Ranges
description: |
The base case `left > right` must return `None`. This happens when:
- A node has no left child: recursive call gets an empty range
- A node has no right child: recursive call gets an empty range
Missing this base case causes infinite recursion or index errors.
key_takeaways:
- "**BST property**: In-order traversal of a BST always produces a sorted sequence — this is fundamental to many BST algorithms"
- "**Divide-and-conquer**: Building a balanced structure from sorted data by recursively picking the middle element is a powerful pattern"
- "**Rebuild vs modify**: Sometimes reconstructing a data structure is simpler and equally efficient as modifying in place"
- "**Related problems**: This technique applies to *Convert Sorted Array to BST* (LC 108) and *Convert Sorted List to BST* (LC 109)"
time_complexity: "O(n). We visit each node exactly twice — once during in-order traversal to extract values, and once during reconstruction."
space_complexity: "O(n). We store all `n` values in an array, plus O(log n) recursion stack depth for the balanced tree construction."
solutions:
- approach_name: In-order Traversal + Rebuild
is_optimal: true
code: |
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def balance_bst(root: TreeNode) -> TreeNode:
# Step 1: Extract all values via in-order traversal (produces sorted array)
values = []
def inorder(node):
if not node:
return
inorder(node.left) # Visit left subtree
values.append(node.val) # Record current node
inorder(node.right) # Visit right subtree
inorder(root)
# Step 2: Build balanced BST from sorted array
def build(left: int, right: int) -> TreeNode | None:
if left > right:
return None # Base case: empty range
# Pick middle element as root for balance
mid = (left + right) // 2
node = TreeNode(values[mid])
# Recursively build left and right subtrees
node.left = build(left, mid - 1)
node.right = build(mid + 1, right)
return node
return build(0, len(values) - 1)
explanation: |
**Time Complexity:** O(n) — In-order traversal visits each node once, and reconstruction visits each value once.
**Space Complexity:** O(n) — The array stores all values. Recursion stack is O(log n) for the balanced tree.
This solution elegantly combines two fundamental operations: BST in-order traversal (which guarantees sorted output) and divide-and-conquer tree construction (which guarantees balance). The middle element becomes the root at each level, ensuring subtrees have equal sizes.
- approach_name: Iterative In-order + Rebuild
is_optimal: false
code: |
def balance_bst(root: TreeNode) -> TreeNode:
# Iterative in-order traversal using explicit stack
values = []
stack = []
current = root
while stack or current:
# Go as far left as possible
while current:
stack.append(current)
current = current.left
# Process node and move right
current = stack.pop()
values.append(current.val)
current = current.right
# Build balanced BST (same as recursive approach)
def build(left: int, right: int) -> TreeNode | None:
if left > right:
return None
mid = (left + right) // 2
node = TreeNode(values[mid])
node.left = build(left, mid - 1)
node.right = build(mid + 1, right)
return node
return build(0, len(values) - 1)
explanation: |
**Time Complexity:** O(n) — Same as recursive approach.
**Space Complexity:** O(n) — Array for values plus O(h) for the traversal stack where h is tree height.
This variant uses iterative in-order traversal with an explicit stack, which can be useful if recursion depth is a concern for extremely unbalanced trees. The reconstruction phase remains the same.