SvenStack

Tech Share

🧩 Problem Description

Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands.

An island is formed by connecting adjacent lands horizontally or vertically.
You may assume all four edges of the grid are surrounded by water.


πŸ’¬ Examples

Example 1

1
2
3
4
5
6
7
Input: grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
Output: 1

Example 2

1
2
3
4
5
6
7
Input: grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
Output: 3

πŸ’‘ Intuition

This is a connected components counting problem on a grid.

  • Each '1' is land.
  • We can run DFS or BFS from every unvisited '1'.
  • Every time we start a new search, it means we found a new island.
  • Mark visited cells as '0' to avoid double counting.

πŸ”’ Java Code (DFS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int numIslands(char[][] grid) {
int m = grid.length, n = grid[0].length;
int count = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
count++;
}
}
}
return count;
}

private void dfs(char[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') {
return;
}
grid[i][j] = '0'; // mark visited
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
}

πŸ”„ Alternative Approaches

  • BFS with Queue instead of recursion.
  • Union-Find (Disjoint Set Union): Treat each land cell as a node, union adjacent lands, then count distinct sets.

⏱ Time and Space Complexity

  • Time Complexity: O(m Γ— n) β€” each cell is visited at most once.
  • Space Complexity:
    • O(m Γ— n) worst-case recursion depth (DFS stack).
    • O(min(m, n)) for BFS queue.
    • Union-Find requires O(m Γ— n) extra memory.

✍️ Summary

  • Classic flood fill problem.
  • Each DFS/BFS call explores one island fully.
  • Count how many times we start a new DFS/BFS.

Related problems

  • lc-695 β€” Max Area of Island
  • lc-463 β€” Island Perimeter
  • lc-733 β€” Flood Fill

🧩 Problem Description

You are given an m x n grid where each cell can have one of three values:

  • 0 representing an empty cell,
  • 1 representing a fresh orange,
  • 2 representing a rotten orange.

Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten.

Return the minimum number of minutes that must elapse until no cell has a fresh orange.
If it is impossible, return -1.


πŸ’¬ Examples

Example 1

1
2
Input: grid = [[2,1,1],[1,1,0],[0,1,1]]
Output: 4

Example 2

1
2
3
Input: grid = [[2,1,1],[0,1,1],[1,0,1]]
Output: -1
Explanation: The orange in the bottom left corner (row 2, column 0) is never rotten.

Example 3

1
2
Input: grid = [[0,2]]
Output: 0

πŸ’‘ Intuition

This is a multi-source BFS problem.

  • Each rotten orange is a BFS starting point.
  • Use a queue to simulate the spread minute by minute.
  • Track how many fresh oranges remain.
  • If at the end some fresh oranges are left, return -1.

πŸ”’ Java Code (BFS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public int orangesRotting(int[][] grid) {
int m = grid.length, n = grid[0].length;
Queue<int[]> q = new LinkedList<>();
int fresh = 0;

for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 2) {
q.offer(new int[]{i, j});
} else if (grid[i][j] == 1) {
fresh++;
}
}
}

if (fresh == 0) return 0; // no fresh oranges

int minutes = -1;
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};

while (!q.isEmpty()) {
int size = q.size();
minutes++;
for (int s = 0; s < size; s++) {
int[] cur = q.poll();
for (int[] d : dirs) {
int x = cur[0] + d[0], y = cur[1] + d[1];
if (x < 0 || y < 0 || x >= m || y >= n || grid[x][y] != 1) continue;
grid[x][y] = 2;
fresh--;
q.offer(new int[]{x, y});
}
}
}

return fresh == 0 ? minutes : -1;
}
}

⏱ Time and Space Complexity

  • Time Complexity: O(m Γ— n), each cell is visited at most once.
  • Space Complexity: O(m Γ— n) for the BFS queue.

✍️ Summary

  • Treat all rotten oranges as starting points.
  • Run BFS layer by layer to simulate time passing.
  • Return minutes when all fresh oranges are rotten, otherwise -1.

Related problems

  • lc-286 β€” Walls and Gates
  • lc-542 β€” 01 Matrix
  • lc-200 β€” Number of Islands

🧩 Problem Description

Given the root of a binary tree, return the maximum path sum of the tree.

A path is any sequence of nodes where each pair of adjacent nodes has an edge connecting them.
A path does not need to pass through the root and cannot revisit a node.
The path sum is the sum of node values along the path.


πŸ’¬ Examples

Example 1

1
2
3
Input: root = [1,2,3]
Output: 6
Explanation: The maximum path is 2 β†’ 1 β†’ 3.

Example 2

1
2
3
Input: root = [-10,9,20,null,null,15,7]
Output: 42
Explanation: The maximum path is 15 β†’ 20 β†’ 7.

πŸ’‘ Intuition

At each node, there are two different concepts:

  1. Max Gain to Parent (what we can return upward):
    max(node.val, node.val + leftGain, node.val + rightGain)

    • This represents the best one-side path that can be extended by the parent.
  2. Max Path Through Node (candidate for global answer):
    node.val + max(0, leftGain) + max(0, rightGain)

    • When a path goes through the current node and uses both sides (if beneficial).
    • Negative gains should be treated as 0 (i.e., discard them).

We do a postorder DFS to compute gains bottom-up while maintaining a global maximum.


πŸ”’ Java Code (DFS + Global Tracking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
private int maxSum = Integer.MIN_VALUE;

public int maxPathSum(TreeNode root) {
gain(root);
return maxSum;
}

private int gain(TreeNode node) {
if (node == null) return 0;

int leftGain = Math.max(0, gain(node.left));
int rightGain = Math.max(0, gain(node.right));

// Path that passes through current node using both sides
int pathThrough = node.val + leftGain + rightGain;
maxSum = Math.max(maxSum, pathThrough);

// Return the max gain to parent: choose one side to extend
return node.val + Math.max(leftGain, rightGain);
}
}

⏱ Time and Space Complexity

  • Time Complexity: O(n) β€” each node is visited once.
  • Space Complexity: O(h) β€” recursion stack, where h is the height of the tree (O(n) worst case).

✍️ Summary

  • Use postorder DFS to compute the best upward gain and update a global maximum with the through-node path.
  • Discard negative gains with Math.max(0, ...) so they don’t depress the total.
  • Classic pattern: **β€œreturn to parent” vs update global answer.

Related problems

  • lc-543 β€” Diameter of Binary Tree (length vs sum idea)
  • lc-687 β€” Longest Univalue Path
  • lc-437 β€” Path Sum III

🧩 Problem Description

Given the root of a binary tree and an integer targetSum, return the number of paths where the sum of the node values in the path equals targetSum.

The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes).


πŸ’¬ Example

Example 1

1
2
3
4
5
6
Input: root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
Output: 3
Explanation: The paths that sum to 8 are:
- 5 β†’ 3
- 5 β†’ 2 β†’ 1
- -3 β†’ 11

Example 2

1
2
Input: root = [1], targetSum = 1
Output: 1

πŸ’‘ Approach 1: DFS (Brute Force)

The most straightforward solution is:

  • For each node, start a DFS to find all downward paths whose sum equals targetSum.
  • This results in O(nΒ²) time complexity in the worst case.

πŸ”’ Java Code (Brute Force)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if (root == null) return 0;
return countFromNode(root, targetSum)
+ pathSum(root.left, targetSum)
+ pathSum(root.right, targetSum);
}

private int countFromNode(TreeNode node, long targetSum) {
if (node == null) return 0;
int count = 0;
if (node.val == targetSum) count++;
count += countFromNode(node.left, targetSum - node.val);
count += countFromNode(node.right, targetSum - node.val);
return count;
}
}

πŸ’‘ Approach 2: Prefix Sum + HashMap (Optimized)

We can reduce time complexity to O(n) by:

  • Maintaining a prefix sum map that stores (currentSum β†’ frequency).
  • At each node, we calculate how many previous prefix sums satisfy:
    1
    currentSum - targetSum = somePreviousPrefix
  • Update the map during DFS and backtrack after visiting children.

πŸ”’ Java Code (Optimized)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int pathSum(TreeNode root, int targetSum) {
Map<Long, Integer> prefixSumMap = new HashMap<>();
prefixSumMap.put(0L, 1); // Base case: sum = 0 occurs once
return dfs(root, 0L, targetSum, prefixSumMap);
}

private int dfs(TreeNode node, long currSum, int targetSum, Map<Long, Integer> map) {
if (node == null) return 0;

currSum += node.val;
int count = map.getOrDefault(currSum - targetSum, 0);

map.put(currSum, map.getOrDefault(currSum, 0) + 1);

count += dfs(node.left, currSum, targetSum, map);
count += dfs(node.right, currSum, targetSum, map);

map.put(currSum, map.get(currSum) - 1); // backtrack

return count;
}
}

⏱ Time and Space Complexity

Brute Force

  • Time: O(nΒ²) in worst case
  • Space: O(h) recursion stack

Prefix Sum Optimized

  • Time: O(n) β€” visiting each node once
  • Space: O(n) β€” prefix sum map and recursion stack

✍️ Summary

  • Brute Force: Try all starting points β†’ simple but slow.
  • Prefix Sum: Tracks how many ways we can reach targetSum efficiently.
  • Related problems:
    • lc-112 β€” Path Sum
    • lc-113 β€” Path Sum II
    • lc-437 β€” Path Sum III (this one)

🧩 Problem Description

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes p and q.

The lowest common ancestor is defined between two nodes p and q as the lowest node in the tree that has both p and q as descendants (where we allow a node to be a descendant of itself).


πŸ’¬ Example

Example 1

1
2
3
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
Output: 3
Explanation: The LCA of nodes 5 and 1 is 3.

Example 2

1
2
3
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
Output: 5
Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself.

πŸ’‘ Approach: Recursive DFS

We traverse the binary tree recursively:

  • If the current node is null, return null.
  • If the current node matches p or q, return it.
  • Recursively search left and right subtrees.
  • If both left and right return non-null, the current node is the LCA.
  • Otherwise, return the non-null child.

πŸ”’ Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {{
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {{
if (root == null || root == p || root == q) {{
return root;
}}

TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);

if (left != null && right != null) {{
return root;
}}

return left != null ? left : right;
}}
}}

⏱ Time and Space Complexity

  • Time Complexity: O(n) β€” visiting each node once
  • Space Complexity: O(h) β€” recursion stack, where h is the tree height

✍️ Summary

  • This is a classic DFS + recursion problem on binary trees.
  • We find the split point where p and q diverge in different subtrees.
  • Related problems:
    • lc-235 β€” LCA of a BST (simpler due to BST property)
    • lc-236 β€” LCA of any binary tree (this one)

🧩 Problem Description

Given two integer arrays preorder and inorder where:

  • preorder is the preorder traversal of a binary tree
  • inorder is the inorder traversal of the same tree

Construct and return the binary tree.


πŸ’¬ Example

1
2
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

πŸ’‘ Approach: DFS + HashMap for Index Lookup

Key Observations:

  1. In preorder traversal, the first element is always the root.
  2. In inorder traversal, elements to the left of root belong to the left subtree, and elements to the right belong to the right subtree.
  3. We can use a HashMap to store value β†’ index mappings in inorder array for O(1) splits.

πŸ”’ Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
private Map<Integer, Integer> inorderIndexMap;
private int preorderIndex;

public TreeNode buildTree(int[] preorder, int[] inorder) {
inorderIndexMap = new HashMap<>();
preorderIndex = 0;
for (int i = 0; i < inorder.length; i++) {
inorderIndexMap.put(inorder[i], i);
}
return build(preorder, 0, inorder.length - 1);
}

private TreeNode build(int[] preorder, int left, int right) {
if (left > right) return null;

int rootVal = preorder[preorderIndex++];
TreeNode root = new TreeNode(rootVal);

int inorderIndex = inorderIndexMap.get(rootVal);

root.left = build(preorder, left, inorderIndex - 1);
root.right = build(preorder, inorderIndex + 1, right);

return root;
}
}

⏱ Time and Space Complexity

  • Time Complexity: O(n) β€” each node is created once, and index lookup is O(1).
  • Space Complexity: O(n) β€” for recursion stack and hashmap.

✍️ Summary

  • This is a classic divide and conquer problem.
  • Preorder gives root order, inorder gives subtree boundaries.
  • Related problems:
    • lc-106 β€” Construct from Inorder and Postorder
    • lc-889 β€” Construct from Preorder and Postorder

🧩 Problem Description

Given the root of a binary tree, flatten it to a linked list in-place.
The linked list should follow the same order as a pre-order traversal.


πŸ’¬ Example

1
2
Input: root = [1,2,5,3,4,null,6]
Output: [1,null,2,null,3,null,4,null,5,null,6]

πŸ’‘ Approach 1: Reverse Preorder DFS

We can do a reverse preorder traversal (right β†’ left β†’ root) and keep track of the previous node to build the flattened list.


πŸ”’ Java Code

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
private TreeNode prev = null;

public void flatten(TreeNode root) {
if (root == null) return;
flatten(root.right);
flatten(root.left);
root.right = prev;
root.left = null;
prev = root;
}
}

πŸ’‘ Approach 2: Iterative with Stack

We can also simulate preorder traversal using a stack and rearrange pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public void flatten(TreeNode root) {
if (root == null) return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);

while (!stack.isEmpty()) {
TreeNode curr = stack.pop();

if (curr.right != null) stack.push(curr.right);
if (curr.left != null) stack.push(curr.left);

if (!stack.isEmpty()) {
curr.right = stack.peek();
}
curr.left = null;
}
}
}


⏱ Time and Space Complexity

  • Time Complexity: O(n) β€” visiting all nodes once.
  • Space Complexity: O(h) for recursion / stack (O(n) in worst case).

✍️ Summary

  • Reverse preorder DFS is a neat trick because it avoids extra temporary storage.
  • Iterative stack solution is more explicit and easier to debug.
  • Related problems:
    • lc-116 β€” Populating Next Right Pointers in Each Node
    • lc-430 β€” Flatten a Multilevel Doubly Linked List

🧩 Problem Description

Given the root of a binary tree, imagine you are standing on the right side of it.
Return the values of the nodes you can see ordered from top to bottom.


πŸ’¬ Example

1
2
Input: root = [1,2,3,null,5,null,4]
Output: [1,3,4]
1
2
Input: root = [1,null,3]
Output: [1,3]
1
2
Input: root = []
Output: []

πŸ’‘ Approach 1: BFS (Level Order Traversal)

We can perform a level-order traversal and take the last node at each level as the visible node from the right side.


πŸ”’ Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;

Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);

while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (i == size - 1) { // last node in this level
result.add(node.val);
}
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return result;
}
}

πŸ’‘ Approach 2: DFS (Right-First Preorder)

We can also use DFS, always visiting right child first so the first node we encounter at each depth is the visible one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
dfs(root, 0, result);
return result;
}

private void dfs(TreeNode node, int depth, List<Integer> result) {
if (node == null) return;
if (depth == result.size()) {
result.add(node.val);
}
dfs(node.right, depth + 1, result);
dfs(node.left, depth + 1, result);
}
}


⏱ Time and Space Complexity

  • Time Complexity: O(n) β€” Each node is visited once.
  • Space Complexity: O(h) β€” h is the height of the tree (O(n) worst case).

✍️ Summary

  • BFS is intuitive and level-based.
  • DFS (right-first) is memory-friendly and elegant.
  • This problem pairs well with:
    • lc-102 β€” Binary Tree Level Order Traversal
    • lc-103 β€” Zigzag Level Order Traversal

🧩 Problem Description

Given the root of a binary search tree (BST) and an integer k, return the kth smallest value (1-indexed) in the BST.


πŸ’¬ Example

1
2
Input: root = [3,1,4,null,2], k = 1
Output: 1
1
2
Input: root = [5,3,6,2,4,null,null,1], k = 3
Output: 3

πŸ’‘ Approach 1: Inorder Traversal (Iterative)

A BST’s inorder traversal produces nodes in sorted order.
We can simply traverse in-order and stop when we reach the k-th node.


πŸ”’ Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int kthSmallest(TreeNode root, int k) {
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;

while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
if (--k == 0) return curr.val;
curr = curr.right;
}
return -1; // should not reach here if k is valid
}
}

πŸ’‘ Approach 2: Recursive Inorder Traversal

We can also use recursion with a counter to track when we reach the k-th element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
private int count = 0;
private int result = 0;

public int kthSmallest(TreeNode root, int k) {
inorder(root, k);
return result;
}

private void inorder(TreeNode node, int k) {
if (node == null) return;
inorder(node.left, k);
count++;
if (count == k) {
result = node.val;
return;
}
inorder(node.right, k);
}
}


⏱ Time and Space Complexity

  • Time Complexity: O(h + k) β€” h is tree height; in the worst case O(n) if tree is unbalanced.
  • Space Complexity: O(h) for stack/recursion.

✍️ Summary

  • Inorder traversal is perfect for BST because it gives sorted order.
  • For large trees with many queries, consider augmenting each node with subtree size to get O(log n) queries.
  • Related problems:
    • lc-98 β€” Validate BST
    • lc-701 β€” Insert into BST
    • lc-450 β€” Delete Node in BST

🧩 Problem Description

Given the root of a binary tree, determine if it is a valid binary search tree (BST).

A valid BST is defined as:

  • The left subtree of a node contains only nodes with values less than the node’s value.
  • The right subtree of a node contains only nodes with values greater than the node’s value.
  • Both the left and right subtrees must also be valid BSTs.

πŸ’¬ Example

1
2
Input: root = [2,1,3]
Output: true
1
2
3
Input: root = [5,1,4,null,null,3,6]
Output: false
Explanation: The value 3 is in the right subtree of 5 but is less than 5.

πŸ’‘ Approach 1: DFS with Value Ranges

We recursively check each node’s value against an allowed range:

  1. Initially, the root has range (-∞, +∞).
  2. For left child: upper bound is parent’s value.
  3. For right child: lower bound is parent’s value.
  4. If any node violates the range rule, it’s not a BST.

πŸ”’ Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public boolean isValidBST(TreeNode root) {
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean validate(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;

return validate(node.left, min, node.val) &&
validate(node.right, node.val, max);
}
}

πŸ’‘ Approach 2: Inorder Traversal Check

  • Perform inorder traversal of the tree.
  • If the resulting sequence is strictly increasing, it’s a valid BST.

πŸ”’ Java Code

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
private long prev = Long.MIN_VALUE;

public boolean isValidBST(TreeNode root) {
if (root == null) return true;
if (!isValidBST(root.left)) return false;
if (root.val <= prev) return false;
prev = root.val;
return isValidBST(root.right);
}
}


⏱ Time and Space Complexity

  • Time Complexity: O(n) β€” every node visited once.
  • Space Complexity: O(h) β€” recursion depth (h is tree height).

✍️ Summary

  • Range-check DFS is the most intuitive and avoids extra storage.
  • Inorder traversal works because BST’s inorder is sorted.
  • Common BST pitfalls:
    • Duplicate values β€” BST usually disallows duplicates.
    • Only checking immediate children is not enough β€” must check whole range.

Related problems: lc-230 (Kth Smallest in BST), lc-701 (Insert into BST), lc-450 (Delete Node in BST)

0%