questions B (backspace - burst-balloons)
This commit is contained in:
206
backend/data/questions/balance-a-binary-search-tree.yaml
Normal file
206
backend/data/questions/balance-a-binary-search-tree.yaml
Normal 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
|
||||
|
||||
|
||||
|
||||
**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]`
|
||||
|
||||
|
||||
|
||||
**Step 3: Handle base case**
|
||||
|
||||
- When `left > right`, the range is empty — return `None`
|
||||
- This terminates the recursion at leaf boundaries
|
||||
|
||||
|
||||
|
||||
**Step 4: Return the new root**
|
||||
|
||||
- The initial call with the full range `[0, n - 1]` returns the root of the balanced BST
|
||||
|
||||
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user