questions A (01-matrix - avoid-flood)

This commit is contained in:
2025-05-24 21:40:39 +01:00
parent 9fc5da3a54
commit 9eaafe4649
55 changed files with 10813 additions and 0 deletions

View File

@@ -0,0 +1,223 @@
title: All Nodes Distance K in Binary Tree
slug: all-nodes-distance-k-in-binary-tree
difficulty: medium
leetcode_id: 863
leetcode_url: https://leetcode.com/problems/all-nodes-distance-k-in-binary-tree/
categories:
- trees
- graphs
- hash-tables
patterns:
- bfs
- dfs
description: |
Given the `root` of a binary tree, the value of a target node `target`, and an integer `k`, return *an array of the values of all nodes that have a distance* `k` *from the target node*.
You can return the answer in **any order**.
constraints: |
- `1 <= number of nodes <= 500`
- `0 <= Node.val <= 500`
- All the values `Node.val` are **unique**
- `target` is the value of one of the nodes in the tree
- `0 <= k <= 1000`
examples:
- input: "root = [3,5,1,6,2,0,8,null,null,7,4], target = 5, k = 2"
output: "[7,4,1]"
explanation: "The nodes that are a distance 2 from the target node (with value 5) have values 7, 4, and 1."
- input: "root = [1], target = 1, k = 3"
output: "[]"
explanation: "There are no nodes at distance 3 from the only node in the tree."
explanation:
intuition: |
Imagine you're standing at a node in the tree and want to find all nodes exactly `k` steps away. In a typical tree traversal, you can only move *downward* to children. But this problem requires moving in **all directions** — down to children and *up* to the parent.
Think of it like this: if you could magically add "parent pointers" to each node, the tree would become an **undirected graph**. Then finding all nodes at distance `k` becomes a standard graph traversal problem — just start at the target and do a BFS (breadth-first search) expanding outward level by level until you reach distance `k`.
The key insight is to **convert the tree into a graph** by first building a mapping from each node to its parent. Once we have parent pointers, we can traverse in all three directions (left child, right child, parent) and use BFS to find all nodes at the exact distance `k`.
approach: |
We solve this using a **Two-Phase Approach**: first build parent pointers, then BFS from the target.
**Step 1: Build parent pointers using DFS**
- Create a hash map `parent` that maps each node to its parent node
- Use DFS to traverse the entire tree, recording each node's parent
- Also locate the actual target node (we're given the value, but need the node reference)
&nbsp;
**Step 2: BFS from the target node**
- Start BFS from the target node at distance 0
- Use a `visited` set to avoid revisiting nodes (critical since we can now move in all directions)
- For each node, explore three neighbours: left child, right child, and parent
- Expand level by level until we reach distance `k`
&nbsp;
**Step 3: Collect results**
- When the BFS reaches distance `k`, all nodes in the current queue are at exactly distance `k` from the target
- Return their values as the result
&nbsp;
This approach transforms the constrained tree structure into a flexible graph, enabling bidirectional traversal.
common_pitfalls:
- title: Only Searching Downward
description: |
A common mistake is to only search the subtree rooted at the target node. This misses nodes that are "above" the target in the tree.
For example, if target is a leaf node and `k = 2`, the answer might include the target's grandparent or uncle nodes — you can't find these by only searching downward.
wrong_approach: "DFS only through target's subtree"
correct_approach: "Build parent pointers to enable upward traversal"
- title: Forgetting to Track Visited Nodes
description: |
Once you add parent pointers, the tree becomes a graph with cycles (child → parent → child). Without a `visited` set, BFS will loop infinitely between parent and child nodes.
Always mark nodes as visited when adding them to the queue, not when processing them — this prevents duplicate queue entries.
wrong_approach: "BFS without visited tracking"
correct_approach: "Use a visited set to prevent revisiting nodes"
- title: Confusing Node Value with Node Reference
description: |
The problem gives `target` as a node value, but you need the actual node reference to start BFS. Make sure to find and store the target node during the parent-building DFS.
If you try to compare `node.val == target` during BFS, you might accidentally match a different node with the same value (though values are unique in this problem, it's good practice to work with references).
wrong_approach: "Using target value directly in BFS"
correct_approach: "Find target node reference during DFS setup"
key_takeaways:
- "**Tree to graph conversion**: When you need to traverse a tree in multiple directions, add parent pointers to treat it as an undirected graph"
- "**BFS for distance queries**: BFS naturally finds all nodes at a specific distance — each level of BFS expansion increases distance by 1"
- "**Visited set is essential**: When converting a tree to a graph, cycles emerge; always track visited nodes"
- "**Two-phase pattern**: Preprocessing (build parent map) followed by the main algorithm (BFS) is a powerful pattern for tree problems"
time_complexity: "O(n). We visit each node twice — once during DFS to build parent pointers, once during BFS to find nodes at distance `k`."
space_complexity: "O(n). We store parent pointers for all `n` nodes, plus the BFS queue and visited set can hold up to `n` nodes."
solutions:
- approach_name: Parent Pointers + BFS
is_optimal: true
code: |
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def distance_k(root: TreeNode, target: TreeNode, k: int) -> list[int]:
# Phase 1: Build parent pointers using DFS
parent = {}
def build_parent(node: TreeNode, par: TreeNode | None) -> None:
if not node:
return
parent[node] = par
build_parent(node.left, node)
build_parent(node.right, node)
build_parent(root, None)
# Phase 2: BFS from target to find all nodes at distance k
queue = deque([(target, 0)]) # (node, distance)
visited = {target}
while queue:
node, dist = queue.popleft()
# Found all nodes at distance k
if dist == k:
# Return all nodes currently at this distance
return [node.val] + [n.val for n, d in queue]
# Explore all three directions: left, right, parent
for neighbour in (node.left, node.right, parent[node]):
if neighbour and neighbour not in visited:
visited.add(neighbour)
queue.append((neighbour, dist + 1))
# No nodes found at distance k
return []
explanation: |
**Time Complexity:** O(n) — DFS visits all nodes once, BFS visits all nodes at most once.
**Space Complexity:** O(n) — Parent map stores n entries, queue and visited set can hold up to n nodes.
We first build a parent pointer map using DFS, then run BFS from the target node. The BFS expands outward level by level, exploring left child, right child, and parent at each step. When we reach distance `k`, all nodes in the queue are our answer.
- approach_name: DFS with Distance Tracking
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 distance_k(root: TreeNode, target: TreeNode, k: int) -> list[int]:
result = []
def find_target_and_collect(node: TreeNode) -> int:
"""
Returns distance from node to target if target is in subtree,
otherwise returns -1.
"""
if not node:
return -1
if node == target:
# Collect all nodes at distance k in target's subtree
collect_downward(node, k)
return 0
# Search left subtree
left_dist = find_target_and_collect(node.left)
if left_dist >= 0:
# Target is in left subtree
if left_dist + 1 == k:
result.append(node.val)
else:
# Search right subtree for nodes at remaining distance
collect_downward(node.right, k - left_dist - 2)
return left_dist + 1
# Search right subtree
right_dist = find_target_and_collect(node.right)
if right_dist >= 0:
# Target is in right subtree
if right_dist + 1 == k:
result.append(node.val)
else:
# Search left subtree for nodes at remaining distance
collect_downward(node.left, k - right_dist - 2)
return right_dist + 1
return -1
def collect_downward(node: TreeNode, dist: int) -> None:
"""Collect all nodes at exactly dist distance going downward."""
if not node or dist < 0:
return
if dist == 0:
result.append(node.val)
return
collect_downward(node.left, dist - 1)
collect_downward(node.right, dist - 1)
find_target_and_collect(root)
return result
explanation: |
**Time Complexity:** O(n) — Each node is visited at most twice.
**Space Complexity:** O(h) — Recursion stack depth equals tree height, plus O(k) for collecting downward.
This approach uses pure DFS without explicitly building parent pointers. When we find the target, we collect all nodes at distance `k` in its subtree. As we return up the recursion, we track our distance from the target and collect nodes from the "other" subtree at the appropriate remaining distance. More complex but uses less space for balanced trees.