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,210 @@
title: Balanced Binary Tree
slug: balanced-binary-tree
difficulty: easy
leetcode_id: 110
leetcode_url: https://leetcode.com/problems/balanced-binary-tree/
categories:
- trees
- recursion
patterns:
- dfs
- tree-traversal
description: |
Given a binary tree, determine if it is **height-balanced**.
A **height-balanced** binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one.
constraints: |
- The number of nodes in the tree is in the range `[0, 5000]`
- `-10^4 <= Node.val <= 10^4`
examples:
- input: "root = [3,9,20,null,null,15,7]"
output: "true"
explanation: "The left subtree has height 1 (just node 9), and the right subtree has height 2 (node 20 with children 15, 7). At every node, the height difference between subtrees is at most 1."
- input: "root = [1,2,2,3,3,null,null,4,4]"
output: "false"
explanation: "The left subtree of the root has height 3, while the right subtree has height 1. The difference is 2, which exceeds the allowed difference of 1."
- input: "root = []"
output: "true"
explanation: "An empty tree is considered balanced by definition."
explanation:
intuition: |
Think of a balanced tree like a well-organised bookshelf where no section is dramatically taller than its neighbours.
The key insight is that a tree is balanced if and only if **every single node** in the tree has balanced subtrees. It's not enough to just check the root — you need to verify this property recursively at every node.
Imagine you're a building inspector checking floor heights. You start at the top floor (root) and work your way down. At each floor, you measure the height of the left wing and the right wing. If the difference is more than one floor, the building fails inspection immediately. You continue checking every sub-section until you've verified the entire structure.
The clever optimisation is to combine the height calculation with the balance check. Instead of calculating heights separately and then checking balance (which would be redundant work), we can propagate a "failure signal" up the tree if any subtree is unbalanced.
approach: |
We solve this using a **Bottom-Up DFS** approach that combines height calculation with balance checking:
**Step 1: Define the recursive helper function**
- Create a function `check_height(node)` that returns the height of the subtree if balanced, or `-1` if unbalanced
- This dual-purpose return value eliminates the need for separate balance checks
&nbsp;
**Step 2: Handle base case**
- If the node is `None`, return `0` (an empty tree has height 0 and is balanced)
&nbsp;
**Step 3: Recursively check left subtree**
- Call `check_height(node.left)` to get the left subtree's height
- If the result is `-1`, the left subtree is unbalanced — propagate `-1` upward immediately
&nbsp;
**Step 4: Recursively check right subtree**
- Call `check_height(node.right)` to get the right subtree's height
- If the result is `-1`, the right subtree is unbalanced — propagate `-1` upward immediately
&nbsp;
**Step 5: Check balance at current node**
- Calculate `abs(left_height - right_height)`
- If the difference exceeds `1`, return `-1` to signal imbalance
- Otherwise, return `max(left_height, right_height) + 1` as the height of this subtree
&nbsp;
**Step 6: Return the final result**
- Call `check_height(root)` and return `True` if the result is not `-1`, otherwise `False`
common_pitfalls:
- title: Top-Down Redundant Computation
description: |
A naive approach calculates the height of each subtree at every node independently:
```python
def is_balanced(root):
if not root:
return True
left_height = height(root.left)
right_height = height(root.right)
if abs(left_height - right_height) > 1:
return False
return is_balanced(root.left) and is_balanced(root.right)
```
This results in **O(n^2) time complexity** for skewed trees because `height()` is called repeatedly on the same nodes. For a tree with 5000 nodes, this causes significant performance degradation.
wrong_approach: "Calculate height separately at each node"
correct_approach: "Combine height calculation with balance checking in a single pass"
- title: Forgetting to Check All Nodes
description: |
Some solutions only check the balance condition at the root node. A tree can have balanced immediate children but have deeply unbalanced subtrees further down.
For example, a root with left child having a long left-skewed subtree and right child being a single node — the root's children might look balanced locally, but the overall tree isn't.
The recursive approach naturally handles this by checking every node.
wrong_approach: "Only check the root node's children"
correct_approach: "Recursively verify balance at every node in the tree"
- title: Incorrect Height Definition
description: |
Be careful with the definition of height. The height of a node is the number of edges on the longest path from that node to a leaf. An empty tree has height 0 (or -1 in some definitions), and a single node has height 0 (or 1).
Using inconsistent definitions will cause off-by-one errors in the balance check.
wrong_approach: "Mix up height definitions between edges and nodes"
correct_approach: "Use consistent height definition: empty tree = 0, leaf node = 1"
key_takeaways:
- "**Bottom-up recursion**: When you need to check a property at every node AND compute something (like height), combine both operations in one traversal"
- "**Early termination with sentinel values**: Using `-1` to signal failure allows the algorithm to short-circuit and avoid unnecessary computation"
- "**Foundation for tree problems**: This pattern of returning either a valid value or a failure signal is common in many tree problems (e.g., validating BST, finding LCA)"
- "**Time-space tradeoff**: The O(n) solution uses O(h) stack space where h is the tree height, which is optimal for this problem"
time_complexity: "O(n). Each node is visited exactly once, and we perform O(1) work at each node."
space_complexity: "O(h) where h is the height of the tree. This is the recursion stack space, which is O(log n) for a balanced tree and O(n) for a skewed tree."
solutions:
- approach_name: Bottom-Up DFS
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 is_balanced(root: TreeNode | None) -> bool:
def check_height(node: TreeNode | None) -> int:
# Base case: empty tree has height 0 and is balanced
if not node:
return 0
# Check left subtree - propagate failure immediately
left_height = check_height(node.left)
if left_height == -1:
return -1
# Check right subtree - propagate failure immediately
right_height = check_height(node.right)
if right_height == -1:
return -1
# Check balance at current node
if abs(left_height - right_height) > 1:
return -1 # Signal that tree is unbalanced
# Return height of this subtree
return max(left_height, right_height) + 1
# Tree is balanced if check_height doesn't return -1
return check_height(root) != -1
explanation: |
**Time Complexity:** O(n) — Each node is visited exactly once.
**Space Complexity:** O(h) — Recursion stack where h is tree height.
This bottom-up approach combines height calculation with balance verification. By returning `-1` as a sentinel value for unbalanced subtrees, we achieve early termination and avoid redundant computation.
- approach_name: Top-Down (Naive)
is_optimal: false
code: |
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def is_balanced(root: TreeNode | None) -> bool:
def height(node: TreeNode | None) -> int:
# Calculate height of subtree
if not node:
return 0
return max(height(node.left), height(node.right)) + 1
# Base case: empty tree is balanced
if not root:
return True
# Check balance at current node
left_height = height(root.left)
right_height = height(root.right)
if abs(left_height - right_height) > 1:
return False
# Recursively check both subtrees
return is_balanced(root.left) and is_balanced(root.right)
explanation: |
**Time Complexity:** O(n^2) — For each node, we calculate heights which visits all descendants.
**Space Complexity:** O(h) — Recursion stack where h is tree height.
This naive approach calculates height separately at each node, leading to redundant traversals. For a skewed tree of n nodes, we perform approximately n + (n-1) + (n-2) + ... + 1 = O(n^2) operations. Included here to illustrate why the bottom-up approach is preferred.