Problems
Master tree traversal by solving these carefully selected LeetCode problems. Each problem builds on the concepts from the Concept Guide and uses patterns from the Code Templates.
Easy
1. Binary Tree Inorder Traversal
- Brief: Given the root of a binary tree, return the inorder traversal of its nodes’ values.
- Why this pattern?: This is the fundamental inorder traversal: visit left subtree, process node, visit right subtree.
- Key Insight: For BSTs, inorder traversal produces sorted output.
flowchart LR
subgraph Tree["Input Tree"]
A((1))
B((2))
C((3))
A --> B
A --> C
end
subgraph Output["Inorder Output"]
O1["[2, 1, 3]"]
end
Tree --> Output
Complexity: Time
O(N)
O(H)
function inorderTraversal(root) {
const result = [];
function inorder(node) {
if (!node) return;
inorder(node.left);
result.push(node.val);
inorder(node.right);
}
inorder(root);
return result;
}from typing import List, Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def inorderTraversal(root: Optional[TreeNode]) -> List[int]:
result = []
def inorder(node):
if not node:
return
inorder(node.left)
result.append(node.val)
inorder(node.right)
inorder(root)
return resultimport java.util.*;
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
inorder(root, result);
return result;
}
private void inorder(TreeNode node, List<Integer> result) {
if (node == null) return;
inorder(node.left, result);
result.add(node.val);
inorder(node.right, result);
}
}#include <vector>
class Solution {
public:
std::vector<int> inorderTraversal(TreeNode* root) {
std::vector<int> result;
inorder(root, result);
return result;
}
private:
void inorder(TreeNode* node, std::vector<int>& result) {
if (!node) return;
inorder(node->left, result);
result.push_back(node->val);
inorder(node->right, result);
}
};func inorderTraversal(root *TreeNode) []int {
result := []int{}
var inorder func(node *TreeNode)
inorder = func(node *TreeNode) {
if node == nil {
return
}
inorder(node.Left)
result = append(result, node.Val)
inorder(node.Right)
}
inorder(root)
return result
}def inorder_traversal(root)
result = []
inorder = ->(node) {
return if node.nil?
inorder.call(node.left)
result << node.val
inorder.call(node.right)
}
inorder.call(root)
result
end2. Binary Tree Preorder Traversal
- Brief: Given the root of a binary tree, return the preorder traversal of its nodes’ values.
- Why this pattern?: Preorder visits root first, then left, then right. Useful for copying trees.
- Key Insight: The first element is always the root.
flowchart LR
subgraph Tree["Input Tree"]
A((1))
B((2))
C((3))
A --> B
A --> C
end
subgraph Output["Preorder Output"]
O1["[1, 2, 3]"]
end
Tree --> Output
Complexity: Time
O(N)
O(H)
function preorderTraversal(root) {
const result = [];
function preorder(node) {
if (!node) return;
result.push(node.val);
preorder(node.left);
preorder(node.right);
}
preorder(root);
return result;
}from typing import List, Optional
def preorderTraversal(root: Optional[TreeNode]) -> List[int]:
result = []
def preorder(node):
if not node:
return
result.append(node.val)
preorder(node.left)
preorder(node.right)
preorder(root)
return resultimport java.util.*;
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
preorder(root, result);
return result;
}
private void preorder(TreeNode node, List<Integer> result) {
if (node == null) return;
result.add(node.val);
preorder(node.left, result);
preorder(node.right, result);
}
}#include <vector>
class Solution {
public:
std::vector<int> preorderTraversal(TreeNode* root) {
std::vector<int> result;
preorder(root, result);
return result;
}
private:
void preorder(TreeNode* node, std::vector<int>& result) {
if (!node) return;
result.push_back(node->val);
preorder(node->left, result);
preorder(node->right, result);
}
};func preorderTraversal(root *TreeNode) []int {
result := []int{}
var preorder func(node *TreeNode)
preorder = func(node *TreeNode) {
if node == nil {
return
}
result = append(result, node.Val)
preorder(node.Left)
preorder(node.Right)
}
preorder(root)
return result
}def preorder_traversal(root)
result = []
preorder = ->(node) {
return if node.nil?
result << node.val
preorder.call(node.left)
preorder.call(node.right)
}
preorder.call(root)
result
end3. Maximum Depth of Binary Tree
- Brief: Given the root of a binary tree, return its maximum depth.
- Why this pattern?: Use postorder traversal to calculate depth from leaves up to root.
- Key Insight: Max depth = 1 + max(left depth, right depth).
flowchart TD
subgraph Tree["Input Tree"]
A((3))
B((9))
C((20))
D((15))
E((7))
A --> B
A --> C
C --> D
C --> E
end
subgraph Result["Result"]
R1["Max Depth = 3"]
end
Complexity: Time
O(N)
O(H)
function maxDepth(root) {
if (!root) return 0;
const leftDepth = maxDepth(root.left);
const rightDepth = maxDepth(root.right);
return 1 + Math.max(leftDepth, rightDepth);
}from typing import Optional
def maxDepth(root: Optional[TreeNode]) -> int:
if not root:
return 0
left_depth = maxDepth(root.left)
right_depth = maxDepth(root.right)
return 1 + max(left_depth, right_depth)class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return 1 + Math.max(leftDepth, rightDepth);
}
}class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) return 0;
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return 1 + std::max(leftDepth, rightDepth);
}
};func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
leftDepth := maxDepth(root.Left)
rightDepth := maxDepth(root.Right)
if leftDepth > rightDepth {
return 1 + leftDepth
}
return 1 + rightDepth
}def max_depth(root)
return 0 if root.nil?
left_depth = max_depth(root.left)
right_depth = max_depth(root.right)
1 + [left_depth, right_depth].max
end4. Same Tree
- Brief: Given the roots of two binary trees, check if they are the same.
- Why this pattern?: Use preorder traversal to compare corresponding nodes simultaneously.
- Key Insight: Both structure and values must match at every position.
flowchart LR
subgraph Tree1["Tree 1"]
A1((1))
B1((2))
C1((3))
A1 --> B1
A1 --> C1
end
subgraph Tree2["Tree 2"]
A2((1))
B2((2))
C2((3))
A2 --> B2
A2 --> C2
end
subgraph Result["Result"]
R1["true"]
end
Tree1 --> Result
Tree2 --> Result
Complexity: Time
O(N)
O(H)
function isSameTree(p, q) {
// Both null: same
if (!p && !q) return true;
// One null: different
if (!p || !q) return false;
// Values differ: different
if (p.val !== q.val) return false;
// Recursively check left and right subtrees
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}from typing import Optional
def isSameTree(p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
# Both null: same
if not p and not q:
return True
# One null: different
if not p or not q:
return False
# Values differ: different
if p.val != q.val:
return False
# Recursively check left and right subtrees
return isSameTree(p.left, q.left) and isSameTree(p.right, q.right)class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
// Both null: same
if (p == null && q == null) return true;
// One null: different
if (p == null || q == null) return false;
// Values differ: different
if (p.val != q.val) return false;
// Recursively check left and right subtrees
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
// Both null: same
if (!p && !q) return true;
// One null: different
if (!p || !q) return false;
// Values differ: different
if (p->val != q->val) return false;
// Recursively check left and right subtrees
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};func isSameTree(p *TreeNode, q *TreeNode) bool {
// Both null: same
if p == nil && q == nil {
return true
}
// One null: different
if p == nil || q == nil {
return false
}
// Values differ: different
if p.Val != q.Val {
return false
}
// Recursively check left and right subtrees
return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right)
}def is_same_tree(p, q)
# Both null: same
return true if p.nil? && q.nil?
# One null: different
return false if p.nil? || q.nil?
# Values differ: different
return false if p.val != q.val
# Recursively check left and right subtrees
is_same_tree(p.left, q.left) && is_same_tree(p.right, q.right)
endMedium
5. Binary Tree Level Order Traversal
- Brief: Given the root of a binary tree, return the level order traversal of its nodes’ values.
- Why this pattern?: Classic BFS using a queue to process nodes level by level.
- Key Insight: Track level size before processing to separate levels.
flowchart TD
subgraph Tree["Input Tree"]
A((3))
B((9))
C((20))
D((15))
E((7))
A --> B
A --> C
C --> D
C --> E
end
subgraph Output["Level Order Output"]
L1["Level 0: [3]"]
L2["Level 1: [9, 20]"]
L3["Level 2: [15, 7]"]
end
Complexity: Time
O(N)
O(N)
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}from typing import List, Optional
from collections import deque
def levelOrder(root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return resultimport java.util.*;
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> currentLevel = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
result.add(currentLevel);
}
return result;
}
}#include <vector>
#include <queue>
class Solution {
public:
std::vector<std::vector<int>> levelOrder(TreeNode* root) {
std::vector<std::vector<int>> result;
if (!root) return result;
std::queue<TreeNode*> queue;
queue.push(root);
while (!queue.empty()) {
int levelSize = queue.size();
std::vector<int> currentLevel;
for (int i = 0; i < levelSize; i++) {
TreeNode* node = queue.front();
queue.pop();
currentLevel.push_back(node->val);
if (node->left) queue.push(node->left);
if (node->right) queue.push(node->right);
}
result.push_back(currentLevel);
}
return result;
}
};func levelOrder(root *TreeNode) [][]int {
if root == nil {
return [][]int{}
}
result := [][]int{}
queue := []*TreeNode{root}
for len(queue) > 0 {
levelSize := len(queue)
currentLevel := []int{}
for i := 0; i < levelSize; i++ {
node := queue[0]
queue = queue[1:]
currentLevel = append(currentLevel, node.Val)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
result = append(result, currentLevel)
}
return result
}def level_order(root)
return [] if root.nil?
result = []
queue = [root]
until queue.empty?
level_size = queue.length
current_level = []
level_size.times do
node = queue.shift
current_level << node.val
queue << node.left if node.left
queue << node.right if node.right
end
result << current_level
end
result
end6. Validate Binary Search Tree
- Brief: Given the root of a binary tree, determine if it is a valid BST.
- Why this pattern?: Inorder traversal of a valid BST produces strictly increasing values.
- Key Insight: Track the previous value and ensure current is always greater.
flowchart TD
subgraph ValidBST["Valid BST"]
A1((5))
B1((3))
C1((7))
A1 --> B1
A1 --> C1
end
subgraph InvalidBST["Invalid BST"]
A2((5))
B2((6))
C2((7))
A2 --> B2
A2 --> C2
end
subgraph Results["Results"]
R1["Inorder: 3,5,7 - Valid"]
R2["Inorder: 6,5,7 - Invalid"]
end
Complexity: Time
O(N)
O(H)
function isValidBST(root) {
let prev = -Infinity;
function inorder(node) {
if (!node) return true;
// Check left subtree
if (!inorder(node.left)) return false;
// Check current node against previous
if (node.val <= prev) return false;
prev = node.val;
// Check right subtree
return inorder(node.right);
}
return inorder(root);
}from typing import Optional
def isValidBST(root: Optional[TreeNode]) -> bool:
prev = float('-inf')
def inorder(node):
nonlocal prev
if not node:
return True
# Check left subtree
if not inorder(node.left):
return False
# Check current node against previous
if node.val <= prev:
return False
prev = node.val
# Check right subtree
return inorder(node.right)
return inorder(root)class Solution {
private long prev = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
return inorder(root);
}
private boolean inorder(TreeNode node) {
if (node == null) return true;
// Check left subtree
if (!inorder(node.left)) return false;
// Check current node against previous
if (node.val <= prev) return false;
prev = node.val;
// Check right subtree
return inorder(node.right);
}
}class Solution {
private:
long long prev = LLONG_MIN;
public:
bool isValidBST(TreeNode* root) {
return inorder(root);
}
private:
bool inorder(TreeNode* node) {
if (!node) return true;
// Check left subtree
if (!inorder(node->left)) return false;
// Check current node against previous
if (node->val <= prev) return false;
prev = node->val;
// Check right subtree
return inorder(node->right);
}
};import "math"
func isValidBST(root *TreeNode) bool {
prev := math.MinInt64
var inorder func(node *TreeNode) bool
inorder = func(node *TreeNode) bool {
if node == nil {
return true
}
// Check left subtree
if !inorder(node.Left) {
return false
}
// Check current node against previous
if node.Val <= prev {
return false
}
prev = node.Val
// Check right subtree
return inorder(node.Right)
}
return inorder(root)
}def is_valid_bst(root)
@prev = -Float::INFINITY
inorder = ->(node) {
return true if node.nil?
# Check left subtree
return false unless inorder.call(node.left)
# Check current node against previous
return false if node.val <= @prev
@prev = node.val
# Check right subtree
inorder.call(node.right)
}
inorder.call(root)
end7. Binary Tree Zigzag Level Order Traversal
- Brief: Return the zigzag level order traversal (alternating left-to-right and right-to-left).
- Why this pattern?: BFS with direction tracking. Reverse alternate levels.
- Key Insight: Use a flag to toggle direction after each level.
flowchart TD
subgraph Tree["Input Tree"]
A((3))
B((9))
C((20))
D((15))
E((7))
A --> B
A --> C
C --> D
C --> E
end
subgraph Output["Zigzag Output"]
L1["Level 0 L->R: [3]"]
L2["Level 1 R->L: [20, 9]"]
L3["Level 2 L->R: [15, 7]"]
end
Complexity: Time
O(N)
O(N)
function zigzagLevelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
let leftToRight = true;
while (queue.length) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
if (!leftToRight) {
currentLevel.reverse();
}
result.push(currentLevel);
leftToRight = !leftToRight;
}
return result;
}from typing import List, Optional
from collections import deque
def zigzagLevelOrder(root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
result = []
queue = deque([root])
left_to_right = True
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
if not left_to_right:
current_level.reverse()
result.append(current_level)
left_to_right = not left_to_right
return resultimport java.util.*;
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
boolean leftToRight = true;
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> currentLevel = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
currentLevel.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
if (!leftToRight) {
Collections.reverse(currentLevel);
}
result.add(currentLevel);
leftToRight = !leftToRight;
}
return result;
}
}#include <vector>
#include <queue>
#include <algorithm>
class Solution {
public:
std::vector<std::vector<int>> zigzagLevelOrder(TreeNode* root) {
std::vector<std::vector<int>> result;
if (!root) return result;
std::queue<TreeNode*> queue;
queue.push(root);
bool leftToRight = true;
while (!queue.empty()) {
int levelSize = queue.size();
std::vector<int> currentLevel;
for (int i = 0; i < levelSize; i++) {
TreeNode* node = queue.front();
queue.pop();
currentLevel.push_back(node->val);
if (node->left) queue.push(node->left);
if (node->right) queue.push(node->right);
}
if (!leftToRight) {
std::reverse(currentLevel.begin(), currentLevel.end());
}
result.push_back(currentLevel);
leftToRight = !leftToRight;
}
return result;
}
};func zigzagLevelOrder(root *TreeNode) [][]int {
if root == nil {
return [][]int{}
}
result := [][]int{}
queue := []*TreeNode{root}
leftToRight := true
for len(queue) > 0 {
levelSize := len(queue)
currentLevel := []int{}
for i := 0; i < levelSize; i++ {
node := queue[0]
queue = queue[1:]
currentLevel = append(currentLevel, node.Val)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
if !leftToRight {
// Reverse the slice
for i, j := 0, len(currentLevel)-1; i < j; i, j = i+1, j-1 {
currentLevel[i], currentLevel[j] = currentLevel[j], currentLevel[i]
}
}
result = append(result, currentLevel)
leftToRight = !leftToRight
}
return result
}def zigzag_level_order(root)
return [] if root.nil?
result = []
queue = [root]
left_to_right = true
until queue.empty?
level_size = queue.length
current_level = []
level_size.times do
node = queue.shift
current_level << node.val
queue << node.left if node.left
queue << node.right if node.right
end
current_level.reverse! unless left_to_right
result << current_level
left_to_right = !left_to_right
end
result
endHard
8. Binary Tree Maximum Path Sum
- Brief: Find the maximum path sum of any non-empty path in the tree.
- Why this pattern?: Postorder traversal to calculate max contribution from each subtree.
- Key Insight: At each node, the path can go through it. Track global max separately.
flowchart TD
subgraph Tree["Input Tree"]
A(("-10"))
B((9))
C((20))
D((15))
E((7))
A --> B
A --> C
C --> D
C --> E
end
subgraph Calculation["Max Path"]
R1["Path: 15 -> 20 -> 7"]
R2["Sum: 42"]
end
Complexity: Time
O(N)
O(H)
function maxPathSum(root) {
let maxSum = -Infinity;
function dfs(node) {
if (!node) return 0;
// Get max contribution from left and right (ignore negative)
const left = Math.max(0, dfs(node.left));
const right = Math.max(0, dfs(node.right));
// Path through current node
const currentPathSum = node.val + left + right;
maxSum = Math.max(maxSum, currentPathSum);
// Return max contribution to parent
return node.val + Math.max(left, right);
}
dfs(root);
return maxSum;
}from typing import Optional
def maxPathSum(root: Optional[TreeNode]) -> int:
max_sum = float('-inf')
def dfs(node):
nonlocal max_sum
if not node:
return 0
# Get max contribution from left and right (ignore negative)
left = max(0, dfs(node.left))
right = max(0, dfs(node.right))
# Path through current node
current_path_sum = node.val + left + right
max_sum = max(max_sum, current_path_sum)
# Return max contribution to parent
return node.val + max(left, right)
dfs(root)
return max_sumclass Solution {
private int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
private int dfs(TreeNode node) {
if (node == null) return 0;
// Get max contribution from left and right (ignore negative)
int left = Math.max(0, dfs(node.left));
int right = Math.max(0, dfs(node.right));
// Path through current node
int currentPathSum = node.val + left + right;
maxSum = Math.max(maxSum, currentPathSum);
// Return max contribution to parent
return node.val + Math.max(left, right);
}
}#include <algorithm>
#include <climits>
class Solution {
private:
int maxSum = INT_MIN;
public:
int maxPathSum(TreeNode* root) {
dfs(root);
return maxSum;
}
private:
int dfs(TreeNode* node) {
if (!node) return 0;
// Get max contribution from left and right (ignore negative)
int left = std::max(0, dfs(node->left));
int right = std::max(0, dfs(node->right));
// Path through current node
int currentPathSum = node->val + left + right;
maxSum = std::max(maxSum, currentPathSum);
// Return max contribution to parent
return node->val + std::max(left, right);
}
};import "math"
func maxPathSum(root *TreeNode) int {
maxSum := math.MinInt32
var dfs func(node *TreeNode) int
dfs = func(node *TreeNode) int {
if node == nil {
return 0
}
// Get max contribution from left and right (ignore negative)
left := max(0, dfs(node.Left))
right := max(0, dfs(node.Right))
// Path through current node
currentPathSum := node.Val + left + right
if currentPathSum > maxSum {
maxSum = currentPathSum
}
// Return max contribution to parent
return node.Val + max(left, right)
}
dfs(root)
return maxSum
}
func max(a, b int) int {
if a > b {
return a
}
return b
}def max_path_sum(root)
@max_sum = -Float::INFINITY
dfs = ->(node) {
return 0 if node.nil?
# Get max contribution from left and right (ignore negative)
left = [0, dfs.call(node.left)].max
right = [0, dfs.call(node.right)].max
# Path through current node
current_path_sum = node.val + left + right
@max_sum = [@max_sum, current_path_sum].max
# Return max contribution to parent
node.val + [left, right].max
}
dfs.call(root)
@max_sum
end9. Serialize and Deserialize Binary Tree
- Brief: Design an algorithm to serialize a binary tree to a string and deserialize it back.
- Why this pattern?: Preorder traversal with null markers captures tree structure completely.
- Key Insight: Use a delimiter to separate values and mark null nodes explicitly.
flowchart LR
subgraph Tree["Binary Tree"]
A((1))
B((2))
C((3))
D((4))
E((5))
A --> B
A --> C
C --> D
C --> E
end
subgraph Serialized["Serialized String"]
S1["1,2,null,null,3,4,null,null,5,null,null"]
end
Tree -->|serialize| Serialized
Serialized -->|deserialize| Tree
Complexity: Time
O(N)
O(N)
function serialize(root) {
const result = [];
function dfs(node) {
if (!node) {
result.push('null');
return;
}
result.push(String(node.val));
dfs(node.left);
dfs(node.right);
}
dfs(root);
return result.join(',');
}
function deserialize(data) {
const values = data.split(',');
let index = 0;
function dfs() {
if (values[index] === 'null') {
index++;
return null;
}
const node = new TreeNode(parseInt(values[index]));
index++;
node.left = dfs();
node.right = dfs();
return node;
}
return dfs();
}class Codec:
def serialize(self, root):
result = []
def dfs(node):
if not node:
result.append('null')
return
result.append(str(node.val))
dfs(node.left)
dfs(node.right)
dfs(root)
return ','.join(result)
def deserialize(self, data):
values = data.split(',')
self.index = 0
def dfs():
if values[self.index] == 'null':
self.index += 1
return None
node = TreeNode(int(values[self.index]))
self.index += 1
node.left = dfs()
node.right = dfs()
return node
return dfs()import java.util.*;
public class Codec {
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serializeHelper(root, sb);
return sb.toString();
}
private void serializeHelper(TreeNode node, StringBuilder sb) {
if (node == null) {
sb.append("null,");
return;
}
sb.append(node.val).append(",");
serializeHelper(node.left, sb);
serializeHelper(node.right, sb);
}
private int index;
public TreeNode deserialize(String data) {
String[] values = data.split(",");
index = 0;
return deserializeHelper(values);
}
private TreeNode deserializeHelper(String[] values) {
if (values[index].equals("null")) {
index++;
return null;
}
TreeNode node = new TreeNode(Integer.parseInt(values[index]));
index++;
node.left = deserializeHelper(values);
node.right = deserializeHelper(values);
return node;
}
}#include <string>
#include <sstream>
#include <vector>
class Codec {
public:
std::string serialize(TreeNode* root) {
std::string result;
serializeHelper(root, result);
return result;
}
TreeNode* deserialize(std::string data) {
std::vector<std::string> values;
std::stringstream ss(data);
std::string item;
while (std::getline(ss, item, ',')) {
values.push_back(item);
}
int index = 0;
return deserializeHelper(values, index);
}
private:
void serializeHelper(TreeNode* node, std::string& result) {
if (!node) {
result += "null,";
return;
}
result += std::to_string(node->val) + ",";
serializeHelper(node->left, result);
serializeHelper(node->right, result);
}
TreeNode* deserializeHelper(std::vector<std::string>& values, int& index) {
if (values[index] == "null") {
index++;
return nullptr;
}
TreeNode* node = new TreeNode(std::stoi(values[index]));
index++;
node->left = deserializeHelper(values, index);
node->right = deserializeHelper(values, index);
return node;
}
};import (
"strconv"
"strings"
)
type Codec struct{}
func (c *Codec) serialize(root *TreeNode) string {
var result []string
var dfs func(node *TreeNode)
dfs = func(node *TreeNode) {
if node == nil {
result = append(result, "null")
return
}
result = append(result, strconv.Itoa(node.Val))
dfs(node.Left)
dfs(node.Right)
}
dfs(root)
return strings.Join(result, ",")
}
func (c *Codec) deserialize(data string) *TreeNode {
values := strings.Split(data, ",")
index := 0
var dfs func() *TreeNode
dfs = func() *TreeNode {
if values[index] == "null" {
index++
return nil
}
val, _ := strconv.Atoi(values[index])
node := &TreeNode{Val: val}
index++
node.Left = dfs()
node.Right = dfs()
return node
}
return dfs()
}class Codec
def serialize(root)
result = []
dfs = ->(node) {
if node.nil?
result << 'null'
return
end
result << node.val.to_s
dfs.call(node.left)
dfs.call(node.right)
}
dfs.call(root)
result.join(',')
end
def deserialize(data)
values = data.split(',')
@index = 0
dfs = -> {
if values[@index] == 'null'
@index += 1
return nil
end
node = TreeNode.new(values[@index].to_i)
@index += 1
node.left = dfs.call
node.right = dfs.call
node
}
dfs.call
end
endRecommended Study Order
- Start with traversals: Solve problems 1-2 to master inorder and preorder.
- Build intuition: Problem 3 (Maximum Depth) and 4 (Same Tree) teach recursive thinking.
- Learn BFS: Problem 5 (Level Order) is the foundation for many tree problems.
- Apply BST properties: Problem 6 (Validate BST) uses inorder for validation.
- Practice variations: Problem 7 (Zigzag) adds complexity to BFS.
- Tackle hard problems: Problems 8-9 combine multiple concepts.
Interview Strategy: Start by identifying if the problem needs DFS (path-based, depth-based) or BFS (level-based). Then choose the appropriate traversal order based on when you need to process the node.