questions S-W

This commit is contained in:
2025-05-30 19:18:33 +01:00
parent 68699f35ec
commit f7e491f1e8
46 changed files with 9696 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
title: Same Tree
slug: same-tree
difficulty: easy
leetcode_id: 100
leetcode_url: https://leetcode.com/problems/same-tree/
categories:
- trees
- recursion
patterns:
- dfs
- tree-traversal
description: |
Given the roots of two binary trees `p` and `q`, write a function to check if they are the same or not.
Two binary trees are considered **the same** if they are structurally identical, and the nodes have the same value.
constraints: |
- The number of nodes in both trees is in the range `[0, 100]`
- `-10^4 <= Node.val <= 10^4`
examples:
- input: "p = [1,2,3], q = [1,2,3]"
output: "true"
explanation: "Both trees have identical structure and values at every node."
- input: "p = [1,2], q = [1,null,2]"
output: "false"
explanation: "The left subtree of p has a node with value 2, but the left subtree of q is empty. The structures differ."
- input: "p = [1,2,1], q = [1,1,2]"
output: "false"
explanation: "Although both trees have the same structure, the values at corresponding positions differ (left child is 2 vs 1, right child is 1 vs 2)."
explanation:
intuition: |
Think of comparing two binary trees like comparing two family trees side by side.
You start at the roots (the grandparents) and ask: "Are these two people the same?" If yes, you then recursively ask the same question about their left children (first child's family) and their right children (second child's family).
The key insight is that **two trees are the same if and only if**:
1. Their root values match
2. Their left subtrees are the same
3. Their right subtrees are the same
This naturally leads to a recursive solution. At each step, we compare the current nodes, then delegate the comparison of subtrees to recursive calls. The recursion terminates when we reach `null` nodes — if both are `null`, that part matches; if only one is `null`, the trees differ.
approach: |
We solve this using **Recursive DFS**:
**Step 1: Handle base cases**
- If both `p` and `q` are `null`, return `True` — two empty trees are identical
- If only one of `p` or `q` is `null`, return `False` — one tree has a node where the other doesn't
&nbsp;
**Step 2: Compare current nodes**
- If `p.val != q.val`, return `False` — the values at this position differ
&nbsp;
**Step 3: Recurse on subtrees**
- Recursively check if the left subtrees are the same: `is_same_tree(p.left, q.left)`
- Recursively check if the right subtrees are the same: `is_same_tree(p.right, q.right)`
- Return `True` only if **both** recursive calls return `True`
&nbsp;
This approach visits every node exactly once, comparing corresponding positions in both trees simultaneously.
common_pitfalls:
- title: Forgetting Null Checks
description: |
A common mistake is to access `p.val` or `q.val` without first checking if `p` or `q` is `null`.
This causes a `NullPointerException` or `AttributeError`. Always handle the `null` cases first before accessing node properties.
wrong_approach: "Comparing values before checking for null"
correct_approach: "Check if both nodes are null, then if one is null, then compare values"
- title: Checking Only Values
description: |
Simply checking if both trees contain the same set of values is not enough.
For example, `[1,2,3]` and `[1,3,2]` have the same values but different structures. The trees must be **structurally identical** with values matching at corresponding positions.
wrong_approach: "Collecting all values and comparing sets"
correct_approach: "Compare structure and values simultaneously during traversal"
- title: Only Checking One Subtree
description: |
After confirming the root values match, you must check **both** the left and right subtrees.
Returning early after checking only the left subtree would miss structural differences on the right side. Use `and` to ensure both subtrees are validated.
wrong_approach: "Returning after checking only left subtree"
correct_approach: "Return True only when both left AND right subtree checks pass"
key_takeaways:
- "**Recursive tree comparison**: When comparing two trees, compare roots first, then recursively compare corresponding subtrees"
- "**Base case handling**: Null checks are essential — two nulls are equal, one null means unequal"
- "**DFS pattern**: This is a classic application of depth-first traversal where we explore each branch fully before moving to siblings"
- "**Foundation for tree problems**: This same pattern extends to problems like symmetric tree, subtree checking, and tree serialisation"
time_complexity: "O(n). We visit each node in both trees at most once, where n is the minimum number of nodes in the two trees."
space_complexity: "O(h). The recursion stack can grow up to the height of the tree. In the worst case (skewed tree), this is O(n). For a balanced tree, it's O(log n)."
solutions:
- approach_name: Recursive 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_same_tree(p: TreeNode | None, q: TreeNode | None) -> bool:
# Base case: both nodes are null - trees match at this position
if p is None and q is None:
return True
# Base case: one is null, other isn't - structure differs
if p is None or q is None:
return False
# Compare current node values
if p.val != q.val:
return False
# Recursively check both subtrees must match
return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right)
explanation: |
**Time Complexity:** O(n) — We visit each node at most once.
**Space Complexity:** O(h) — Recursion stack depth equals tree height.
The recursive approach elegantly handles all cases: empty trees, single nodes, and complex structures. By checking null conditions first, we avoid null pointer errors and correctly identify structural differences.
- approach_name: Iterative BFS
is_optimal: false
code: |
from collections import deque
def is_same_tree(p: TreeNode | None, q: TreeNode | None) -> bool:
# Queue holds pairs of nodes to compare
queue = deque([(p, q)])
while queue:
node1, node2 = queue.popleft()
# Both null - continue to next pair
if node1 is None and node2 is None:
continue
# One null, other not - trees differ
if node1 is None or node2 is None:
return False
# Values differ at this position
if node1.val != node2.val:
return False
# Add children pairs to compare next
queue.append((node1.left, node2.left))
queue.append((node1.right, node2.right))
# All pairs matched
return True
explanation: |
**Time Complexity:** O(n) — We process each node pair once.
**Space Complexity:** O(w) — Queue size is bounded by the maximum width of the tree.
This iterative approach uses a queue to compare nodes level by level. It avoids recursion stack overflow for very deep trees, though in practice the constraint (max 100 nodes) makes this unnecessary. The logic mirrors the recursive version but uses explicit queue management.