Lv.3 Flashcards
Lv.3 problems of Firecode.io (47 cards)
Fill in the Ancestors of the Node in a Binary Tree
Given a binary treeโs root node, an empty ArrayList and an integer nodeData, write a method that finds a target node - N with data = nodeData and populates the ArrayList with the data of the ancestor nodes of N - added from the bottom - up.
// Definition for a binary tree node class TreeNode { constructor(val) { this.val = val; this.left = null; this.right = null; } } /** * Finds the node with value `nodeData` and populates `ancestors` (Array) with its ancestors from bottom up. * @param {TreeNode} root - The root of the binary tree * @param {Array} ancestors - The array to populate with ancestors' values * @param {number} nodeData - The data to search for * @returns {boolean} - true if node is found, false otherwise */ function findAncestors(root, ancestors, nodeData) { // TODO: Implement recursive logic to find node and populate ancestors return false; }
๐ช Stepper Hints
1. Think about how tree traversal works. Which traversal technique might help you find something deep in a tree?
2. You want to search the whole tree for a node with a specific value. Maybe recursion helps?
3. What if your recursive function returned true when it found the node? Can you use this to build the ancestor path?
4. As you recurse back up after finding the node, what if you added the current nodeโs value to the list of ancestors?
5. Try writing a helper function that takes the current node, the target value, and the list to fill in. The function should return whether the target is found in the current subtree.
6. If the current node is the target, return true. If either child returns true, push current node to list and return true again.
7.Make sure to only add nodes after you know the target is in their subtree.
// Helper: Build tree from level-order array (null means no node) function buildTree(nodes) { if (!nodes.length || nodes[0] == null) return null; const root = new TreeNode(nodes[0]); const queue = [root]; let i = 1; while (queue.length && i < nodes.length) { const current = queue.shift(); if (nodes[i] != null) { current.left = new TreeNode(nodes[i]); queue.push(current.left); } i++; if (i < nodes.length && nodes[i] != null) { current.right = new TreeNode(nodes[i]); queue.push(current.right); } i++; } return root; } // Test cases function runTests() { console.log("Running Tests...\n"); // Test 1: Empty tree let root1 = null; let ancestors1 = []; findAncestors(root1, ancestors1, 5); console.log("Test 1 (Empty):", ancestors1.length === 0 ? "PASS" : "FAIL"); // Test 2: Node not found let root2 = buildTree([1, 2, 3]); let ancestors2 = []; findAncestors(root2, ancestors2, 99); console.log("Test 2 (Not found):", ancestors2.length === 0 ? "PASS" : "FAIL"); // Test 3: Node is root let root3 = buildTree([7, 3, 10]); let ancestors3 = []; findAncestors(root3, ancestors3, 7); console.log("Test 3 (Target is root):", ancestors3.length === 0 ? "PASS" : "FAIL"); // Test 4: Deep target node let root4 = buildTree([1, 2, 3, 4, 5, null, 6]); let ancestors4 = []; findAncestors(root4, ancestors4, 6); console.log("Test 4 (Deep node):", JSON.stringify(ancestors4) === JSON.stringify([3, 1]) ? "PASS" : "FAIL"); // Test 5: Middle-level node let root5 = buildTree([1, 2, 3, 4, 5]); let ancestors5 = []; findAncestors(root5, ancestors5, 4); console.log("Test 5 (Middle-level node):", JSON.stringify(ancestors5) === JSON.stringify([2, 1]) ? "PASS" : "FAIL"); } runTests();
๐ Related LeetCode Problems
https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/
https://leetcode.com/problems/all-nodes-distance-k-in-binary-tree/description/
โ
JavaScript Solution
~~~
// Tree Node class
class TreeNode {
constructor(data, left = null, right = null) {
this.data = data;
this.left = left;
this.right = right;
}
}
/**
* Finds ancestors of a node with given data and fills the list from bottom to top.
* @param {TreeNode} root - Root of the binary tree
* @param {number[]} ancestors - Array to fill with ancestor values
* @param {number} nodeData - Target nodeโs data
* @returns {boolean} - True if node is found, else false
*/
function findAncestors(root, ancestors, nodeData) {
if (!root) return false;
// If current node is the target node
if (root.data === nodeData) return true;
// Check left or right subtree
if (
findAncestors(root.left, ancestors, nodeData) ||
findAncestors(root.right, ancestors, nodeData)
) {
ancestors.push(root.data); // Add ancestor
return true;
}
return false; // Node not found in this subtree
}
~~~
๐งช Example Usage
// Building a test binary tree // 1 // / \ // 2 3 // / \ // 4 5 const root = new TreeNode(1, new TreeNode(2, new TreeNode(4), new TreeNode(5)), new TreeNode(3) ); const ancestors = []; findAncestors(root, ancestors, 5); console.log(ancestors); // Output: [2, 1]
โฑ Time & Space Complexity
Time Complexity: O(n) โ You may need to visit every node in the tree in the worst case to find the target.
Space Complexity: O(h) โ Due to the recursive call stack, where h is the height of the tree (O(log n) for balanced, O(n) for skewed).
๐ Alternate Approach (with path storage)
1. Another approach is:
Find the path from root to the target node (store it in a list).
Remove the last item (target node itself).
Reverse the list if needed (to get bottom-up ancestors).
But the recursive backtracking approach used above is more efficient and elegant as it avoids an extra pass.
- Iterative with Stack (Advanced)
Use a stack for DFS and a map to track parents.
After locating the target, trace back using the map.
More complex, useful for iterative constraints.
Print Binary Tree Level By Level
Given a binary tree, write a method to print the tree level by level. Each item in the list is an ArrayList of the format [A[], B,[] โฆ..], where A[],B[]โฆ. are the nodes at a particular level, stored in an ArrayList.
// Definition for a binary tree node class TreeNode { constructor(val) { this.val = val; this.left = null; this.right = null; } } /** * Performs level-order traversal and returns result as an array of arrays. * @param {TreeNode} root - The root of the binary tree * @return {number[][]} - Nested arrays, each representing a level of the tree */ function levelOrderTraversal(root) { const result = []; // TODO: Implement level-order traversal using a queue return result; }
๐ช Stepper Hints
1. How would you visit nodes from top to bottom in a tree?
2. Think of a real-world line where people come one after the otherโwhat data structure works like that?
3. You need to explore nodes level by level, and for that, a queue can help.
4. Each time you take a node from the queue, think about who comes next in its level.
5. Youโll want to know when a level ends. Could you track how many nodes are in the current level before starting to process it?
6. Youโll process nodes in the queue n times (where n = number of nodes at the current level), and push their children back to the queue.
7. Build a list (like [ [1], [2, 3], [4, 5, 6] ]) as you go by collecting each levelโs nodes in a sub-list.
// Helper: Build binary tree from level-order array function buildTree(nodes) { if (!nodes.length || nodes[0] === null) return null; const root = new TreeNode(nodes[0]); const queue = [root]; let i = 1; while (queue.length && i < nodes.length) { const current = queue.shift(); if (i < nodes.length && nodes[i] !== null) { current.left = new TreeNode(nodes[i]); queue.push(current.left); } i++; if (i < nodes.length && nodes[i] !== null) { current.right = new TreeNode(nodes[i]); queue.push(current.right); } i++; } return root; } // Test cases function runTests() { console.log("Running Tests...\n"); // Test 1: Empty tree let root1 = null; let result1 = levelOrderTraversal(root1); console.log("Test 1 (Empty):", JSON.stringify(result1) === JSON.stringify([]) ? "PASS" : "FAIL"); // Test 2: Single node tree let root2 = buildTree([5]); let result2 = levelOrderTraversal(root2); console.log("Test 2 (Single node):", JSON.stringify(result2) === JSON.stringify([[5]]) ? "PASS" : "FAIL"); // Test 3: Two levels let root3 = buildTree([1, 2, 3]); let result3 = levelOrderTraversal(root3); console.log("Test 3 (Two levels):", JSON.stringify(result3) === JSON.stringify([[1], [2, 3]]) ? "PASS" : "FAIL"); // Test 4: Three levels let root4 = buildTree([1, 2, 3, 4, null, null, 5]); let result4 = levelOrderTraversal(root4); console.log("Test 4 (Three levels):", JSON.stringify(result4) === JSON.stringify([[1], [2, 3], [4, 5]]) ? "PASS" : "FAIL"); // Test 5: Unbalanced tree let root5 = buildTree([1, 2, null, 3, null, 4]); let result5 = levelOrderTraversal(root5); console.log("Test 5 (Unbalanced):", JSON.stringify(result5)); // Visual inspection expected } runTests();
๐ Related LeetCode Problem
https://leetcode.com/problems/binary-tree-level-order-traversal/
โ JavaScript Solution
// Tree Node class class TreeNode { constructor(data, left = null, right = null) { this.data = data; this.left = left; this.right = right; } } /** * Returns an array of arrays, where each sub-array contains nodes at that level. * @param {TreeNode} root - The root of the binary tree. * @returns {number[][]} - List of levels with node values. */ function levelOrderTraversal(root) { const result = []; if (!root) return result; const queue = [root]; while (queue.length > 0) { const levelSize = queue.length; const currentLevel = []; for (let i = 0; i < levelSize; i++) { const node = queue.shift(); // Dequeue node currentLevel.push(node.data); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(currentLevel); } return result; }
๐งช Example Usage
// Test binary tree: // 1 // / \ // 2 3 // / \ \ // 4 5 6 const root = new TreeNode(1, new TreeNode(2, new TreeNode(4), new TreeNode(5)), new TreeNode(3, null, new TreeNode(6)) ); console.log(levelOrderTraversal(root)); // Output: [[1], [2, 3], [4, 5, 6]]
โฑ Time & Space Complexity
Time Complexity: O(n) โ Every node is visited exactly once.
Space Complexity: O(n) โ The queue stores up to one levelโs worth of nodes at a time (worst case: the bottom level = ~n/2 nodes).
๐ Alternate Approaches
1. Recursive Level Order (Less Common)
You can solve this recursively by tracking the current level, but itโs more complex and less intuitive than the queue-based BFS approach.
function recursiveLevelOrder(root) { const result = []; function helper(node, level) { if (!node) return; if (!result[level]) result[level] = []; result[level].push(node.data); helper(node.left, level + 1); helper(node.right, level + 1); } helper(root, 0); return result; }
When to Prefer:
Only when recursion is specifically required or youโre asked to solve it without using a queue.
Happy Numbers
Write a method to determine whether a postive number is Happy. A number is Happy (Yes, it is a thing!) if it follows a sequence that ends in 1 after following the steps given below:
Beginning with the number itself, replace it by the sum of the squares of its digits until either the number becomes 1 or loops endlessly in a cycle that does not include 1.
/** * Determines whether a number is a Happy number. * @param {number} n - The input positive integer * @return {boolean} - True if the number is Happy, false otherwise */ function isHappy(n) { // TODO: Implement logic to detect if number becomes 1 or loops endlessly return false; }
๐ช Stepper Hints
1. Try breaking the number into its digits. Can you think of how to isolate each digit?
2. Youโll need to square each digit and add them. Do this repeatedly. What stops the loop?
3. What if the number becomes 1? Thatโs a sign youโve got a Happy Number.
4. Some numbers never reach 1. How would you detect if youโre stuck in a cycle?
5. Use a Set to store numbers youโve seen. If you see the same number again โ youโre in a loop.
6. Create a function that sums the squares of digits. Keep transforming the number using this function until itโs either 1 or a repeat.
// Helper: Compute sum of squares of digits of a number function sumOfSquares(num) { return num .toString() .split('') .map(Number) .reduce((sum, digit) => sum + digit * digit, 0); } // Test cases function runTests() { const tests = [ { input: 1, expected: true }, { input: 19, expected: true }, // Known happy number { input: 2, expected: false }, // Known non-happy number { input: 7, expected: true }, { input: 4, expected: false }, { input: 100, expected: true }, { input: 1111111, expected: false } ]; console.log("Running isHappy() tests...\n"); tests.forEach(({ input, expected }, idx) => { const result = isHappy(input); console.log( `Test ${idx + 1}: isHappy(${input}) => ${result} | ${result === expected ? 'PASS' : 'FAIL'}` ); }); } runTests();
๐ Related LeetCode Problem
https://leetcode.com/problems/happy-number/
โ JavaScript Solution
/** * Determines if a number is a Happy Number. * @param {number} n - A positive integer. * @returns {boolean} - True if happy, false otherwise. */ function isHappy(n) { const seen = new Set(); while (n !== 1) { if (seen.has(n)) return false; seen.add(n); n = sumOfSquares(n); } return true; } /** * Helper function to compute sum of squares of digits. * @param {number} num - The number to process. * @returns {number} - The sum of the squares of its digits. */ function sumOfSquares(num) { let sum = 0; while (num > 0) { const digit = num % 10; sum += digit * digit; num = Math.floor(num / 10); } return sum; }
๐งช Example Usage
console.log(isHappy(19)); // true (19 โ 82 โ 68 โ 100 โ 1) console.log(isHappy(2)); // false
โฑ Time & Space Complexity
Time Complexity:
Worst-case O(log n) per digit-sum iteration.
Total iterations are bounded (since the values quickly fall below 243). So, it converges fast.
Effectively O(1) for all practical inputs.
Space Complexity:
O(log n) per number stored in the Set, bounded since possible values are limited.
Again, effectively O(1).
๐ Alternate Approach: Floydโs Cycle Detection (Tortoise & Hare)
Instead of using a Set to track cycles, use two pointers moving at different speeds:
function isHappy(n) { let slow = n, fast = n; do { slow = sumOfSquares(slow); fast = sumOfSquares(sumOfSquares(fast)); } while (slow !== fast); return slow === 1; }
Time Complexity: Same
Space Complexity: O(1) โ no extra memory used
Find the kth Largest Node in a BST
Given a Binary Search Tree and an integer k, implement a method to find and return its kth largest node. We assume the tree is a BST (hint: you can more focus on the right side traversal).
// Definition for a binary tree node. class TreeNode { constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } /** * Finds the kth largest node in a Binary Search Tree. * @param {TreeNode} root - The root of the BST. * @param {number} k - The kth position to find. * @return {number|null} - The value of the kth largest node or null if not found. */ function findKthLargest(root, k) { // TODO: Implement reverse in-order traversal (Right -> Root -> Left) return null; }
๐ช Stepper Hints
1. How does a BST store values? What can you say about values to the left vs. right of a node?
2. In an in-order traversal (Left โ Node โ Right), the values come out in ascending order.
3. Can you reverse this order to get values in descending order?
4. Try a reverse in-order traversal (Right โ Node โ Left) to visit largest values first.
5. Keep a counter while traversing. When youโve visited k nodes, youโve found the answer.
6. Implement a recursive function that visits the right subtree first, then the current node, then the left subtree, and stop when youโve visited k nodes.
// Helper to build a sample BST function buildSampleBST() { // Example BST: // 5 // / \ // 3 7 // / \ / \ // 2 4 6 8 const root = new TreeNode(5); root.left = new TreeNode(3); root.right = new TreeNode(7); root.left.left = new TreeNode(2); root.left.right = new TreeNode(4); root.right.left = new TreeNode(6); root.right.right = new TreeNode(8); return root; } // Test cases function runTests() { const root = buildSampleBST(); const testCases = [ { k: 1, expected: 8 }, { k: 2, expected: 7 }, { k: 3, expected: 6 }, { k: 4, expected: 5 }, { k: 5, expected: 4 }, { k: 6, expected: 3 }, { k: 7, expected: 2 }, { k: 8, expected: null }, // Out of bounds ]; console.log("Running findKthLargest() tests...\n"); testCases.forEach(({ k, expected }, i) => { const result = findKthLargest(root, k); console.log( `Test ${i + 1}: findKthLargest(root, ${k}) => ${result} | ${ result === expected ? 'PASS' : `FAIL (expected ${expected})` }` ); }); } runTests();
๐ Related LeetCode Problem
https://leetcode.com/problems/kth-smallest-element-in-a-bst/
โ JavaScript Solution (Recursive)
// Tree Node class class TreeNode { constructor(data, left = null, right = null) { this.data = data; this.left = left; this.right = right; } } /** * Finds the kth largest node in a BST. * @param {TreeNode} root - Root of the BST. * @param {number} k - The rank of the largest node to find. * @returns {number|null} - The kth largest node's value or null if not found. */ function findKthLargest(root, k) { let count = 0; let result = null; function reverseInOrder(node) { if (!node || result !== null) return; reverseInOrder(node.right); count++; if (count === k) { result = node.data; return; } reverseInOrder(node.left); } reverseInOrder(root); return result; }
๐งช Example Usage
// Example BST: // 5 // / \ // 3 8 // / \ \ // 2 4 9 const root = new TreeNode(5, new TreeNode(3, new TreeNode(2), new TreeNode(4)), new TreeNode(8, null, new TreeNode(9)) ); console.log(findKthLargest(root, 1)); // 9 console.log(findKthLargest(root, 3)); // 5 console.log(findKthLargest(root, 6)); // 2
โฑ Time & Space Complexity
Time Complexity: O(h + k)
Where h is the height of the tree. In the worst case (skewed tree), h = n.
You stop once k nodes have been visited, so this is often much less than n.
Space Complexity:
O(h) for the recursion stack (no extra memory structures used).
๐ Alternate Approaches
1. Iterative Reverse In-Order Traversal using Stack
If recursion is not allowed or youโd prefer explicit stack control:
function findKthLargest(root, k) { const stack = []; let node = root; while (stack.length > 0 || node) { while (node) { stack.push(node); node = node.right; } node = stack.pop(); k--; if (k === 0) return node.data; node = node.left; } return null; }
More verbose, but great for practice with stack traversal.
- Store all values in an array and index it
Do a full in-order traversal and get all values โ return arr[arr.length - k]
Not recommended unless the tree is small or k is dynamic.
- Augmented BST (Advanced)
Store count of nodes in each subtree for fast kth queries (used in order-statistics trees)
Insert a Node at a specified position in a Linked List
Given a singly-linked list, implement a method to insert a node at a specific position and return the head of the list. If the given position is greater than the list size, simply insert the node at the end.
// Definition for singly-linked list node class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } /** * Inserts a new node with given value at the specified position. * If position >= list length, insert at the end. * @param {ListNode} head - The head of the linked list. * @param {number} value - The value to insert. * @param {number} position - The position to insert at. * @return {ListNode} - The new head of the list. */ function insertAtPosition(head, value, position) { // TODO: Implement logic to insert node at specific position return head; }
๐ช Stepper Hints
1. Think about how you can traverse through a linked list โ what tells you when youโve reached the end?
2. To insert at a position, youโll need to reach the node just before that position. Can you track the position as you walk?
3. How do you โinsertโ something between two nodes? What needs to change with the pointers?
4. Consider edge cases like inserting at the head (position 0) or when the position is beyond the listโs length.
5. Youโll need to create a new node and connect it to the correct place. Think of it like โsnapping inโ a link in a chain.
6. If the position is beyond the list, just go until the end and attach the new node there.
// Helper to convert array to linked list function arrayToList(arr) { let dummy = new ListNode(0); let current = dummy; for (let val of arr) { current.next = new ListNode(val); current = current.next; } return dummy.next; } // Helper to convert linked list to array function listToArray(head) { const result = []; while (head) { result.push(head.val); head = head.next; } return result; } // Test cases function runTests() { const tests = [ { input: [[1, 2, 4], 3, 2], expected: [1, 2, 3, 4] }, { input: [[1], 0, 0], expected: [0, 1] }, { input: [[], 5, 0], expected: [5] }, { input: [[1, 2], 3, 5], expected: [1, 2, 3] }, // position > length { input: [[1, 2], 9, 1], expected: [1, 9, 2] }, ]; tests.forEach(({ input, expected }, i) => { const [arr, value, pos] = input; const head = arrayToList(arr); const newHead = insertAtPosition(head, value, pos); const result = listToArray(newHead); const pass = JSON.stringify(result) === JSON.stringify(expected); console.log(`Test ${i + 1}: ${pass ? 'PASS' : 'FAIL'} โ Expected ${expected}, Got ${result}`); }); } runTests();
๐ Related LeetCode Problem
https://leetcode.com/problems/design-linked-list/
โ JavaScript Solution
class ListNode { constructor(value, next = null) { this.value = value; this.next = next; } } /** * Inserts a new node with `value` at `position` in the linked list. * @param {ListNode} head - The head of the singly linked list. * @param {number} value - Value to insert. * @param {number} position - Position to insert at (0-based index). * @returns {ListNode} - New head of the list. */ function insertAtPosition(head, value, position) { const newNode = new ListNode(value); // Case 1: Insert at head if (position === 0 || !head) { newNode.next = head; return newNode; } let current = head; let index = 0; // Traverse until the node *before* the desired position while (current.next !== null && index < position - 1) { current = current.next; index++; } // Insert the node newNode.next = current.next; current.next = newNode; return head; }
๐งช Example Usage
// Helper to print list function printList(head) { const result = []; while (head) { result.push(head.value); head = head.next; } console.log(result.join(" -> ")); } // Initial list: 1 -> 2 -> 4 let head = new ListNode(1, new ListNode(2, new ListNode(4))); head = insertAtPosition(head, 3, 2); // Insert 3 at position 2 printList(head); // 1 -> 2 -> 3 -> 4 head = insertAtPosition(head, 0, 0); // Insert 0 at head printList(head); // 0 -> 1 -> 2 -> 3 -> 4 head = insertAtPosition(head, 5, 100); // Insert at end printList(head); // 0 -> 1 -> 2 -> 3 -> 4 -> 5
โฑ Time & Space Complexity
Time Complexity: O(min(n, position))
You traverse up to the given position, or until the end.
Space Complexity: O(1)
No extra space used except the new node.
๐ Alternate Approaches
1. Dummy Node Approach (Robust for Edge Cases)
Add a dummy head node to avoid special-case handling for head insertions.
function insertAtPosition(head, value, position) { const dummy = new ListNode(-1); dummy.next = head; let prev = dummy; let index = 0; while (prev.next !== null && index < position) { prev = prev.next; index++; } const newNode = new ListNode(value); newNode.next = prev.next; prev.next = newNode; return dummy.next; }
โ
Cleaner handling for insert-at-head
โ
Good for modular code
- Recursion (Not ideal here)
You can insert recursively, but itโs inefficient and harder to read for insertion at a position.
Generate Combinations of Parentheses
Write a method to return all valid combinations of n-pairs of parentheses; the method should return an ArrayList of strings, in which each string represents a valid combination of parentheses. The order of the strings in the ArrayList does not matter.
/** * Generates all valid combinations of n pairs of parentheses. * @param {number} n - Number of pairs of parentheses. * @return {string[]} - List of valid combinations. */ function generateParentheses(n) { const result = []; // TODO: Implement recursive backtracking logic // Optionally define a helper function here return result; }
๐ช Stepper Hints
1. What makes a combination of parentheses โvalidโ? Think in terms of balancing.
2. Youโre building a string one character at a time. What decisions can you make at each step?
3. You have two kinds of moves: add โ(โ or โ)โ. Which one can you always add?
4. You can only add โ)โ if there are more โ(โ already placed โ this keeps the string valid.
5. Use recursion to explore all valid choices. Stop when the string length is 2 * n.
6. Backtracking helps explore all combinations. If one path doesnโt work, the function goes back and tries another.
// ------------------ // Test Infrastructure // ------------------ function runTests() { const tests = [ { n: 0, expected: [""] }, { n: 1, expected: ["()"] }, { n: 2, expected: ["(())", "()()"] }, { n: 3, expected: ["((()))", "(()())", "(())()", "()(())", "()()()"] }, { n: 4, expectedCount: 14 }, // just check count ]; tests.forEach(({ n, expected, expectedCount }, i) => { const output = generateParentheses(n); const isMatch = expected ? expected.every(item => output.includes(item)) && output.length === expected.length : output.length === expectedCount; console.log(`Test ${i + 1} (n=${n}): ${isMatch ? 'PASS' : 'FAIL'} โ Output:`, output); }); } runTests();
๐ Related LeetCode Problem
https://leetcode.com/problems/generate-parentheses/
โ JavaScript Solution (Recursive)
/** * Generates all valid combinations of n pairs of parentheses. * @param {number} n - Number of pairs. * @returns {string[]} - Array of valid parentheses strings. */ function generateParentheses(n) { const result = []; function backtrack(current, open, close) { // Base case: if the current string is of required length if (current.length === 2 * n) { result.push(current); return; } // Add '(' if we still have some left if (open < n) { backtrack(current + '(', open + 1, close); } // Add ')' only if there are more opens used than closes if (close < open) { backtrack(current + ')', open, close + 1); } } backtrack('', 0, 0); return result; }
๐งช Example Usage
console.log(generateParentheses(3)); // Output (order may vary): ["((()))", "(()())", "(())()", "()(())", "()()()"]
โฑ Time & Space Complexity
Time Complexity: O(2^n) exponential growth due to recursive tree
However, only valid combinations are built, so actual number is the n-th Catalan number:
C_n = (1/(n+1)).(2_n - n)
Space Complexity:
O(n) for recursion stack
O(C_n) for result storage
๐ Alternate Approaches
1. Iterative with Queue (BFS-style)
Instead of recursion, simulate paths using a queue. Not as elegant, but useful to learn BFS.
- Memoized DP
Generate from smaller n values. Much more complex and less intuitive, so backtracking is preferred.
Are These Binary Trees Identical
Given two binary trees, determine if they are identical. If they are, return true otherwise return false.
/** * Definition for a binary tree node. */ class TreeNode { constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } /** * Determines if two binary trees are identical. * @param {TreeNode} p - Root of the first tree. * @param {TreeNode} q - Root of the second tree. * @return {boolean} */ function isIdentical(p, q) { // TODO: Implement recursive comparison logic return false; }
๐ช Stepper Hints
1. Can you think of how youโd walk through both trees? What would you compare at each step?
2. What would make two trees not identical? Consider structure, and then values.
3. What is the base case when traversing a tree? What does it mean when both nodes are null?
4. If both nodes are non-null, what two things should you compare?
5. Try recursively comparing left children and right children. What must be true for both subtrees?
// ------------------ // Test Infrastructure // ------------------ function runTests() { const tree1 = new TreeNode(1, new TreeNode(2), new TreeNode(3)); const tree2 = new TreeNode(1, new TreeNode(2), new TreeNode(3)); const tree3 = new TreeNode(1, new TreeNode(2)); const tree4 = new TreeNode(1, null, new TreeNode(3)); const tree5 = new TreeNode(1, new TreeNode(3), new TreeNode(2)); const tests = [ { p: tree1, q: tree2, expected: true }, { p: tree1, q: tree3, expected: false }, { p: tree1, q: tree4, expected: false }, { p: tree1, q: tree5, expected: false }, { p: null, q: null, expected: true }, { p: tree1, q: null, expected: false }, ]; tests.forEach(({ p, q, expected }, i) => { const result = isIdentical(p, q); console.log(`Test ${i + 1}: ${result === expected ? "PASS" : "FAIL"} โ Expected: ${expected}, Got: ${result}`); }); } runTests();
๐ Related LeetCode Problem
https://leetcode.com/problems/same-tree/
โ JavaScript Implementation
class TreeNode { constructor(value, left = null, right = null) { this.value = value; this.left = left; this.right = right; } } /** * Determines if two binary trees are identical in structure and values. * @param {TreeNode} p - Root of the first tree. * @param {TreeNode} q - Root of the second tree. * @returns {boolean} - True if trees are identical, false otherwise. */ function isIdentical(p, q) { // Base case: both nodes are null if (p === null && q === null) return true; // One is null, but the other isn't if (p === null || q === null) return false; // Check current node values and recursively check left and right return ( p.value === q.value && isIdentical(p.left, q.left) && isIdentical(p.right, q.right) ); }
๐งช Example Usage
const tree1 = new TreeNode(1, new TreeNode(2), new TreeNode(3) ); const tree2 = new TreeNode(1, new TreeNode(2), new TreeNode(3) ); console.log(isIdentical(tree1, tree2)); // true
โฑ Time and Space Complexity
Time Complexity: O(n)
Where n is the number of nodes in the tree (since we visit every node once).
Space Complexity: O(h)
Where h is the height of the tree (recursion stack). In the worst case (skewed tree), O(n).
๐ Alternate Approaches
1. Iterative Using Queue (BFS-style)
Instead of recursion, use a queue to compare node pairs iteratively. More verbose but avoids recursion depth limits.
- Tree Serialization + String Comparison
You serialize both trees (e.g., via preorder) and compare the strings. Not ideal โ fails when different structures give same values.
Insert a Head in a Doubly Linked List
Implement a method insertAtHead that inserts a head in Doubly Linked list.
// Node class for Doubly Linked List class Node { constructor(data) { this.data = data; this.prev = null; this.next = null; } } // Doubly Linked List class class DoublyLinkedList { constructor() { this.head = null; } /** * Inserts a new node with given data at the head of the list. * @param {*} data */ insertAtHead(data) { // TODO: Create new node // const newNode = new Node(data); // TODO: If head is null, set newNode as head // TODO: Else, link newNode before current head and update head } // Helper method to return array representation toArray() { const result = []; let current = this.head; while (current) { result.push(current.data); current = current.next; } return result; } }
๐ช Stepper Hints
1.Think about what happens when youโre adding something at the beginning of a list. Who should point to whom?
2.There are two links to update: one from the new node to the old head, and one from the old head to the new node.
3.What should the prev of the new head be?
4.If the list is empty, what should the new nodeโs next and prev be?
5.Donโt forget to reassign the listโs head pointer after insertion.
// ----------- Test Cases ----------- function runTests() { const list = new DoublyLinkedList(); list.insertAtHead(10); console.assert(JSON.stringify(list.toArray()) === JSON.stringify([10]), "Test 1 Failed"); list.insertAtHead(20); console.assert(JSON.stringify(list.toArray()) === JSON.stringify([20, 10]), "Test 2 Failed"); list.insertAtHead(30); console.assert(JSON.stringify(list.toArray()) === JSON.stringify([30, 20, 10]), "Test 3 Failed"); console.log("All tests passed."); } runTests();
๐ Related LeetCode Problems
https://leetcode.com/problems/flatten-a-multilevel-doubly-linked-list/
https://leetcode.com/problems/design-linked-list/
โ
JavaScript Implementation
๐งฑ Doubly Linked List Node
class DoublyNode { constructor(data) { this.data = data; this.prev = null; this.next = null; } } class DoublyLinkedList { constructor() { this.head = null; } /** * Inserts a new node with given data at the head of the list. * @param {*} data */ insertAtHead(data) { const newNode = new Node(data); if (this.head === null) { this.head = newNode; } else { newNode.next = this.head; this.head.prev = newNode; this.head = newNode; } } // Optional: Print the list for debugging printForward() { let current = this.head; const result = []; while (current) { result.push(current.data); current = current.next; } console.log(result.join(" <-> ")); } }
๐งช Example Usage
const list = new DoublyLinkedList(); list.insertAtHead(10); list.insertAtHead(20); list.insertAtHead(30); list.printForward(); // 30 <-> 20 <-> 10
โฑ Time & Space Complexity
Time Complexity:
O(1) โ constant time because weโre only touching the head and its next node.
Space Complexity:
O(1) extra space โ just one node at a time.
๐ Alternate Approaches?
Inserting at the tail or at a specific index is a common variant.
Find the Nth Node from the End - Linked List
Given a singly-linked list, implement the method that returns Nth node from the end of the list. You are allowed to use extra memory for this implementation.
// Node class for singly-linked list class Node { constructor(data) { this.data = data; this.next = null; } } // Singly-linked list class class LinkedList { constructor() { this.head = null; } // Utility to append a node to the end of the list append(data) { const newNode = new Node(data); if (!this.head) { this.head = newNode; return; } let current = this.head; while (current.next) { current = current.next; } current.next = newNode; } // Utility to convert the list to array toArray() { const result = []; let current = this.head; while (current) { result.push(current.data); current = current.next; } return result; } } /** * Returns the nth node from the end of a singly-linked list. * @param {Node} head - The head of the linked list * @param {number} n - The index from the end (1-based) * @returns {*} - Data of the nth node from the end */ function getNthFromEnd(head, n) { // TODO: Implement using extra memory (e.g., array to store nodes) // Return null if n is out of bounds or list is empty return null; // Placeholder }
๐ช Stepper Hints
1. Think about how youโd find the โlastโ node in a singly-linked list. What do you do?
2. Once you know how many nodes are in the list, how can you find the Nth from the end?
3. Can you store the nodes as you go, so you can refer back later?
4. What kind of data structure allows indexing by position?
5. Try pushing nodes into an array during traversal โ then just index from the back.
// ----------- Test Cases ----------- function runTests() { const list = new LinkedList(); list.append(10); list.append(20); list.append(30); list.append(40); list.append(50); console.assert(getNthFromEnd(list.head, 1) === 50, "Test 1 Failed"); // last node console.assert(getNthFromEnd(list.head, 3) === 30, "Test 2 Failed"); console.assert(getNthFromEnd(list.head, 5) === 10, "Test 3 Failed"); // first node console.assert(getNthFromEnd(list.head, 6) === null, "Test 4 Failed"); // out of bounds console.assert(getNthFromEnd(null, 1) === null, "Test 5 Failed"); // empty list console.log("All tests passed (if no errors)."); } runTests();
๐ Related LeetCode Problem
https://leetcode.com/problems/remove-nth-node-from-end-of-list/
โ JavaScript Solution
class ListNode { constructor(data, next = null) { this.data = data; this.next = next; } } /** * Returns the Nth node from the end using extra memory (array). * @param {ListNode} head - The head of the singly linked list. * @param {number} n - The Nth position from the end (1-based index). * @returns {*} - The data of the Nth node from the end, or null if not found. */ function getNthFromEnd(head, n) { const nodes = []; // Step 1: Store all nodes in an array let current = head; while (current !== null) { nodes.push(current); current = current.next; } // Step 2: Calculate position from start const indexFromStart = nodes.length - n; if (indexFromStart < 0 || indexFromStart >= nodes.length) { return null; // Out of bounds } return nodes[indexFromStart].data; }
๐งช Example Usage
const head = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5))))); console.log(getNthFromEnd(head, 2)); // Output: 4
โฑ๏ธ Time and Space Complexity
Time Complexity: O(n) โ Single pass through the list
Space Complexity: O(n) โ Extra memory to store all nodes
๐ Alternate Approaches
1. Two-Pointer Technique (Optimal)
Move one pointer n steps ahead, then start the second pointer from head.
Move both together until the first reaches the end.
Now the second pointer is at the Nth from end.
โ
Time: O(n), Space: O(1) (in-place)
If youโre ready, we can implement the optimized constant-space version too. Want to try that?
Snake
Letโs have some fun with 2D Matrices! Write a method findSpiral to traverse a 2D matrix of ints in a clockwise spiral order and append the elements to an output ArrayList if Integers.
/** * Traverses a 2D matrix in spiral order (clockwise) * and returns the elements in an array. * * @param {number[][]} matrix - 2D array of integers * @returns {number[]} Spiral ordered elements */ function findSpiral(matrix) { const result = []; // TODO: Implement spiral traversal logic // Use four boundaries: top, bottom, left, right return result; }
๐ช Stepper Hints
Hint 1๏ธโฃ
Visualize the movement: youโre walking around the matrix in a circular pattern. What directions do you move in?
Hint 2๏ธโฃ
How many boundaries does a matrix have? What happens to them after you complete one spiral round?
Hint 3๏ธโฃ
Youโll need variables to track the top, bottom, left, and right bounds. How should they change as you move inward?
Hint 4๏ธโฃ
After completing a row or column, which boundary should you shrink?
Hint 5๏ธโฃ
When do you stop? Whatโs the condition that tells you youโve processed all elements?
// ---------- Test Cases ---------- // Helper to compare arrays function arraysEqual(a, b) { return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, i) => val === b[i]); } // 1x1 matrix console.assert(arraysEqual(findSpiral([[1]]), [1]), "Test 1 Failed"); // 2x2 matrix console.assert( arraysEqual(findSpiral([[1, 2], [4, 3]]), [1, 2, 3, 4]), "Test 2 Failed" ); // 3x3 matrix console.assert( arraysEqual(findSpiral([[1, 2, 3], [8, 9, 4], [7, 6, 5]]), [1, 2, 3, 4, 5, 6, 7, 8, 9]), "Test 3 Failed" ); // 3x2 matrix console.assert( arraysEqual(findSpiral([[1, 2], [6, 3], [5, 4]]), [1, 2, 3, 4, 5, 6]), "Test 4 Failed" ); // Empty matrix console.assert(arraysEqual(findSpiral([]), []), "Test 5 Failed"); // 1x4 matrix console.assert(arraysEqual(findSpiral([[1, 2, 3, 4]]), [1, 2, 3, 4]), "Test 6 Failed"); // 4x1 matrix console.assert(arraysEqual(findSpiral([[1], [2], [3], [4]]), [1, 2, 3, 4]), "Test 7 Failed"); console.log("All tests passed (if no errors).");
๐ Related LeetCode Problem
https://leetcode.com/problems/spiral-matrix/
โ
JavaScript Solution
~~~
/**
* Traverses a 2D matrix in spiral order and returns the result in an array.
* @param {number[][]} matrix
* @returns {number[]} Array of elements in spiral order
*/
function findSpiral(matrix) {
const result = [];
if (matrix.length === 0 || matrix[0].length === 0) {
return result;
}
let top = 0;
let bottom = matrix.length - 1;
let left = 0;
let right = matrix[0].length - 1;
while (top <= bottom && left <= right) {
// Traverse from Left to Right
for (let col = left; col <= right; col++) {
result.push(matrix[top][col]);
}
top++;
// Traverse from Top to Bottom for (let row = top; row <= bottom; row++) { result.push(matrix[row][right]); } right--; // Traverse from Right to Left (only if there's a remaining row) if (top <= bottom) { for (let col = right; col >= left; col--) { result.push(matrix[bottom][col]); } bottom--; } // Traverse from Bottom to Top (only if there's a remaining column) if (left <= right) { for (let row = bottom; row >= top; row--) { result.push(matrix[row][left]); } left++; } }
return result;
}
~~~
๐งช Example Usage
const matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; console.log(findSpiral(matrix)); // Output: [1, 2, 3, 6, 9, 8, 7, 4, 5]
โฑ Time and Space Complexity
Time Complexity: O(m * n) โ Every element is visited once.
Space Complexity: O(m * n) โ Result array stores all elements.
Where m = rows, n = cols.
๐ Alternate Approaches
1. Recursive Spiral Traversal
Peel off outer layers recursively.
Elegant but harder to manage for beginners.
- Visited Matrix + Direction Vectors
Track visited cells and change direction as needed.
More flexible but slower due to overhead.
Iterative Inorder Traversal
Given a binary tree, write a method to perform the inorder traversal iteratively. Append the data of each node visited to an ArrayList. Return an empty Arraylist in the case of an empty tree.
๐ช Stepper Hints
https://leetcode.com/problems/binary-tree-inorder-traversal/
๐ Related LeetCode Problems
๐น Hint 1: What is inorder traversal?
Itโs Left โ Root โ Right
Recursively:
inorder(node) { inorder(node.left); visit(node); inorder(node.right); }
๐น Hint 2: Recursion uses a call stack. What if we simulate it?
Use an explicit stack ([]) to track nodes instead of recursion.
๐น Hint 3: Traverse left down as far as possible
While current is not null, keep pushing current to the stack and move to current.left.
๐น Hint 4: When left is null, pop from the stack
Visit the node and move to current.right.
โ JavaScript Solution
function inorderTraversal(root) { const result = []; const stack = []; let current = root; while (current !== null || stack.length > 0) { // Reach the leftmost node while (current !== null) { stack.push(current); current = current.left; } // Current is null now current = stack.pop(); result.push(current.data); // Visit node // Visit the right subtree current = current.right; } return result; }
โฑ๏ธ Time and Space Complexity
Time Complexity: O(n) โ Visit every node once
Space Complexity: O(h) โ Where h is the height of the tree (stack space)
๐ Alternate Approaches
1. Recursive Traversal
Simpler, but uses call stack.
function inorderRecursive(node, result = []) { if (!node) return result; inorderRecursive(node.left, result); result.push(node.data); inorderRecursive(node.right, result); return result; }
- Morris Traversal (Advanced, no extra space)
Uses threaded binary trees.
Time: O(n), Space: O(1)
More complex, but optimal in space.
Iterative BST Validation
Given the root node of a Binary Tree, write a method - validateBSTItr to iteratively determine if it is a Binary Search Tree. A BST must satisfy the following conditions:
* The right subtree of a node contains nodes data > its data.
* The left subtree of a node contains nodes with data < its data.
* A nodeโs left and right subtrees follow the above two conditions.
๐ช Stepper Hints
https://leetcode.com/problems/validate-binary-search-tree/
๐ Related LeetCode Problems
๐น Hint 1: What traversal can help detect BST validity?
Inorder traversal of a valid BST should produce a sorted list (strictly increasing).
๐น Hint 2: Can you do inorder traversal iteratively?
Yes! Use a stack and track prev (previous nodeโs value) during traversal.
๐น Hint 3: What condition breaks the BST property?
If the current nodeโs value is less than or equal to prev, itโs not a BST.
โ JavaScript Solution
function validateBSTItr(root) { const stack = []; let current = root; let prev = null; while (current !== null || stack.length > 0) { // Traverse left subtree while (current !== null) { stack.push(current); current = current.left; } current = stack.pop(); // Validate BST condition if (prev !== null && current.data <= prev) { return false; } prev = current.data; // Move to right subtree current = current.right; } return true; }
โฑ๏ธ Time and Space Complexity
Time Complexity: O(n) โ every node is visited once
Space Complexity: O(h) โ height of the tree (stack size in worst case)
๐ Alternate Approaches
1. Recursive Approach with Bounds
Pass down min and max bounds as you recurse
function validateBSTRecursive(node, min = -Infinity, max = Infinity) { if (!node) return true; if (node.data <= min || node.data >= max) return false; return validateBSTRecursive(node.left, min, node.data) && validateBSTRecursive(node.right, node.data, max); }
Time: O(n)
Space: O(h) due to call stack
- Morris Traversal for BST Validation (Advanced, no extra space)
Avoids recursion and stack. Harder to implement, rarely required in interviews.
Print Paths
Youโre given a 2D board which contains an m x n matrix of chars - char[][] board. Write a method - printPaths that prints all possible paths from the top left cell to the bottom right cell. Your method should return an ArrayList of Strings, where each String represents a path with characters appended in the order of movement. Youโre only allowed to move down and right on the board. The order of String insertion in the ArrayList does not matter.
๐ช Stepper Hints
๐ Related LeetCode Problems
๐น Hint 1: What type of problem is this?
This is a recursive backtracking problem, where we explore multiple valid paths.
๐น Hint 2: What base case stops recursion?
When you reach the bottom-right corner (m-1, n-1), you add the path string to your result list.
๐น Hint 3: What choices do you have at each step?
From a cell (i, j), you can move:
* Right โ (i, j+1)
* Down โ (i+1, j)
๐น Hint 4: Track the current path
* Use a string (pathSoFar) to track characters as you move along the board.
โ JavaScript Solution
function printPaths(board) { const result = []; if (!board || board.length === 0 || board[0].length === 0) { return result; } const m = board.length; const n = board[0].length; function dfs(i, j, path) { // Add current character path += board[i][j]; // Base Case: reached bottom-right cell if (i === m - 1 && j === n - 1) { result.push(path); return; } // Move Down if (i + 1 < m) { dfs(i + 1, j, path); } // Move Right if (j + 1 < n) { dfs(i, j + 1, path); } } dfs(0, 0, ''); return result; }
โฑ๏ธ Time and Space Complexity
Time Complexity: O(2^(m+n))
Every move splits into two โ down or right. You generate all possible paths from top-left to bottom-right.
Space Complexity:
Recursive stack depth: O(m + n)
Result storage: up to O(2^(m+n)) strings
๐ Alternate Approaches
1. Dynamic Programming (if counting number of paths)
Not useful here since we want the actual paths, not just the count.
- Iterative BFS
Less intuitive for this problem, since youโre building strings. Recursion is more elegant here.
Introduction to Tries
A Trie or Prefix Tree an efficient data lookup structure - often used to store large collections of words or dictionaries. With a Trie, search complexities can be reduced to O(k) where k is the key or word length. The autocorrect on your iOS or Android keyboard uses a Trie of the most commonly used words along with fuzzy match algorithms to autocorrect and autosuggest words as you type. Youโre given a completed TrieNode class that represents one node of a Trie, and a partially complete Trie class. Your task is to complete the insertWord, searchWord and searchPrefix methods on the Trie class. Take a look at the examples below to see what each of these do.
๐ช Stepper Hints
https://leetcode.com/problems/implement-trie-prefix-tree/
๐ Related LeetCode Problems
๐น Hint 1: What is a TrieNode?
Each TrieNode represents a character and holds:
* A map or object of children (next letters)
* A boolean flag isEndOfWord to mark when a full word ends here
๐น Hint 2: How do you insert a word?
Loop through each letter of the word:
* If the letter is not already in the nodeโs children, create it.
* Move to the child node.
* After the last letter, mark isEndOfWord = true.
๐น Hint 3: How do you search for a word?
Traverse letter by letter:
* If a letter is missing, return false.
* After last letter, return true only if isEndOfWord is true
๐น Hint 4: How do you search for a prefix?
Almost the same as search, but donโt check isEndOfWord at the end.
โ JavaScript Solution
class TrieNode { constructor() { this.children = {}; // maps char to TrieNode this.isEndOfWord = false; } } class Trie { constructor() { this.root = new TrieNode(); } insertWord(word) { let node = this.root; for (let char of word) { if (!(char in node.children)) { node.children[char] = new TrieNode(); } node = node.children[char]; } node.isEndOfWord = true; } searchWord(word) { let node = this.root; for (let char of word) { if (!(char in node.children)) { return false; } node = node.children[char]; } return node.isEndOfWord; } searchPrefix(prefix) { let node = this.root; for (let char of prefix) { if (!(char in node.children)) { return false; } node = node.children[char]; } return true; } }
โฑ๏ธ Time and Space Complexity
Let k be the length of the word/prefix.
* insertWord โ O(k) time, O(k) space (new nodes may be created)
* searchWord โ O(k) time
* searchPrefix โ O(k) time
๐ Alternate Approaches
1. Use Map instead of plain object
Slightly cleaner in languages that support it natively, but Object is more common in JS.this.children = new Map();
- Ternary Search Tree (Advanced)
A memory-efficient variation that uses binary search tree-like nodes. Slower insert but less memory.
Binary Tree Serialization
In Computer Science, serialization is the process of converting objects or data structures into a sequence (or series) of characters that can be stored easily in a file / database table or transmitted across a network. Serialized objects need to be de-serialized to create a semantically identical clone of the original object, before being used in programs. Youโre given the root node of a binary tree - TreeNode root in the method serializeTree.
This method should serialize the binary tree and output a String str, which is then used as an input parameter for the method restoreTree. restoreTree should create a Binary Tree that is structurally identical to the one you serialized and return the root node of the tree. Your task is to fill in the logic for these 2 methods. Donโt worry about passing the serialized String to restoreTree - that will be done automatically when you run your code. Feel free to use any notation you prefer when serializing the binary tree. The choice of traversal algorithm is open, but try and limit the time complexity of both methods to O(n).
Your serialized String will be used to restore the tree. Be sure to use the same format and notation in restoreTree that you use to serialize in serializeTree().
๐ช Stepper Hints
https://leetcode.com/problems/serialize-and-deserialize-binary-tree/
๐ Related LeetCode Problems
๐น Hint 1: Which traversal to choose?
The easiest approach is to use preorder traversal (root โ left โ right).
Itโs a common choice because it captures root-first structure.
๐น Hint 2: How do we handle null nodes?
Use a special marker like โXโ or โnullโ for null children.
This way, tree structure can be uniquely reconstructed.
๐น Hint 3: How to convert to and from a string?
You can use an array of strings and then call .join(โ,โ) to create your serialized form.
๐น Hint 4: For restoreTree(), how to track the current node?
Use a queue (or an index) over the string tokens.
Each call processes a value and recursively builds left and right children.
โ JavaScript Implementation
class TreeNode { constructor(data) { this.data = data; this.left = null; this.right = null; } } function serializeTree(root) { const result = []; function dfs(node) { if (node === null) { result.push("X"); return; } result.push(node.data); dfs(node.left); dfs(node.right); } dfs(root); return result.join(","); } function restoreTree(dataStr) { const values = dataStr.split(","); let index = 0; function buildTree() { if (values[index] === "X") { index++; return null; } const node = new TreeNode(parseInt(values[index])); index++; node.left = buildTree(); node.right = buildTree(); return node; } return buildTree(); }
โฑ๏ธ Time & Space Complexity
Time Complexity: O(n)
Both serialization and deserialization visit each node once.
Space Complexity:
O(n) for the result string/array
O(h) for recursion stack (where h = height of tree)
๐ Alternate Approaches
1. Level Order (BFS) Traversal Serialization
Use a queue and serialize level-by-level. Slightly more complex to implement and parse.
- Custom Formats (JSON-like)
Makes the format human-readable but larger. More error-prone if not careful with structure.
Remove Duplicate Nodes
Given a singly linked list, write a method removeDuplicates that removes duplicate nodes. Nodes are treated duplicate when they have same data.
๐ช Stepper Hints
www.geeksforgeeks.org/remove-duplicates-from-an-unsorted-linked-list/
๐ Related LeetCode Problems
๐น Hint 1: How can we detect a duplicate?
You need to track the values youโve already seen.
Think of using a Set to store each unique value.
๐น Hint 2: Can we use extra memory?
Yes! Since the prompt allows it, using extra space (like a Set) is totally fine.
This simplifies your solution and keeps it efficient (O(n) time).
๐น Hint 3: How to remove a node?
Youโll need two pointers:
* prev: Last unique node
* current: Node youโre examining
If current.data is already in the Set, skip it:prev.next = current.next;
โ JavaScript Implementation
class ListNode { constructor(data) { this.data = data; this.next = null; } } function removeDuplicates(head) { if (head === null) return null; const seen = new Set(); let current = head; let prev = null; while (current !== null) { if (seen.has(current.data)) { // Duplicate found, skip current node prev.next = current.next; } else { seen.add(current.data); prev = current; } current = current.next; } return head; }
โฑ๏ธ Time & Space Complexity
Time Complexity: O(n)
We visit each node exactly once.
Space Complexity: O(n)
The Set stores up to n unique values.
๐ Alternate Approaches
1. Without Extra Space (In-place Comparison)
Approach: Use two nested loops:
Outer loop: Traverse each node
Inner loop: Compare it with every other node and remove duplicates
Time: O(nยฒ)
Space: O(1)
โ Use only if memory usage must be minimized.
- Sorted Linked List Variant
If the list is sorted, you can just check:if (current.data === current.next.data)
Time: O(n), Space: O(1)
Rotate a Square Image Clockwise
You are given a square 2D image matrix where each integer represents a pixel. Write a method rotateSquareImageCW to rotate the image clockwise - in-place. This problem can be broken down into simpler sub-problems youโve already solved earlier! Rotating an image clockwise can be achieved by taking the transpose of the image matrix and then flipping it on its vertical axis.
๐ช Stepper Hints
https://leetcode.com/problems/rotate-image/
๐ Related LeetCode Problems
๐น Hint 1: What does a 90ยฐ clockwise rotation mean?
The first row becomes the last column, second row becomes second-last column, and so on.
Original: Rotated: 1 2 3 7 4 1 4 5 6 ==> 8 5 2 7 8 9 9 6 3
๐น Hint 2: Break it down!
Break the rotation into two simpler operations:
1. Transpose the matrix
2. Reverse each row
๐น Hint 3: Transposing a matrix?
Swap elements across the diagonal (i.e., matrix[i][j] with matrix[j][i])
Only need to loop where j > i to avoid double-swapping.
๐น Hint 4: How to reverse each row?
Use two pointers, or call .reverse() if allowed.
But practice doing it manually to build muscle memory.
โ JavaScript Solution
function rotateSquareImageCW(matrix) { const n = matrix.length; // Step 1: Transpose the matrix for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]; } } // Step 2: Reverse each row for (let i = 0; i < n; i++) { let left = 0, right = n - 1; while (left < right) { [matrix[i][left], matrix[i][right]] = [matrix[i][right], matrix[i][left]]; left++; right--; } } }
โฑ๏ธ Time and Space Complexity
Time Complexity: O(n^2)
Transposing: O(n^2)
Reversing rows: O(n^2) (each row reversed in O(n))
Space Complexity: O(1)
In-place swaps only; no extra matrix.
๐ Alternate Approach (Layer-by-Layer Rotation)
Rotate the matrix one square โlayerโ at a time (outer layer โ inner layer). More complex but avoids the transpose+reverse trick.
Time: O(n^2)
Space: O(1)
Might be good practice for building a more fundamental understanding of array indexing.
Number of Half Nodes in a Binary Tree
Write a function to find the total number of half nodes in a binary tree. A half node is a node which has exactly one child node. If there are no half nodes, return 0.
๐ช Stepper Hints
๐ Related LeetCode Problems
๐น Hint 1: What is a half node?
A node with exactly one child (i.e., either left child or right child is null, but not both).
So:
* A node with two children โ (not half node)
* A leaf node (no children) โ
* A node with one child โ
๐น Hint 2: How do we check this in code?
For a given node:
if ((node.left === null && node.right !== null) || (node.left !== null && node.right === null)) { // this is a half node }
๐น Hint 3: How should we traverse the tree?
You can use DFS (recursion) or BFS (queue).
Start from the root, traverse all nodes, and count how many are half nodes.
๐น Hint 4: Base case for recursion
If the node is null, return 0.
โ
JavaScript Solution (DFS Recursive)
function countHalfNodes(root) {
if (!root) return 0;
let count = 0; if ((root.left === null && root.right !== null) || (root.left !== null && root.right === null)) { count = 1; } return count + countHalfNodes(root.left) + countHalfNodes(root.right); }
โฑ Time and Space Complexity
Time Complexity: O(n) โ Every node is visited once.
Space Complexity:
* Recursive call stack: O(h) where h is the height of the tree
* In worst case (unbalanced tree), O(n) stack depth.
๐ Alternate Approach: BFS (Iterative)
If recursion is tricky for the student, they could also use level-order traversal using a queue:
function countHalfNodes(root) { if (!root) return 0; let queue = [root]; let count = 0; while (queue.length > 0) { const node = queue.shift(); if ((node.left === null && node.right !== null) || (node.left !== null && node.right === null)) { count++; } if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } return count; }
Convert a Binary Tree to its Mirror Image
Write a function to convert a binary tree into its mirror image and return the root node of the mirrored tree.
๐ช Stepper Hints
https://leetcode.com/problems/invert-binary-tree/
๐ Related LeetCode Problems
๐น Hint 1: What does โmirrorโ mean for a tree node?
For each node in the tree:
* Swap its left and right child nodes.
๐น Hint 2: What traversal should we use?
Since you need to process every node and its children, a postorder or preorder traversal (DFS) works best.
๐น Hint 3: What is the base case?
If the node is null, return null. This will naturally end the recursion.
๐น Hint 4: How to apply recursion?
At each node:
1. Recursively mirror the left subtree
2. Recursively mirror the right subtree
3. Swap the left and right children
โ JavaScript Solution
function mirrorTree(root) { if (root === null) return null; // Recursively mirror the subtrees const leftMirror = mirrorTree(root.left); const rightMirror = mirrorTree(root.right); // Swap left and right root.left = rightMirror; root.right = leftMirror; return root; }
โฑ Time & Space Complexity
Time Complexity: O(n) โ Each node is visited once
Space Complexity:
Recursive stack: O(h) where h is the tree height
Worst case: O(n) if tree is skewed
๐ Alternate Approach: Iterative (using BFS)
If recursion is difficult or stack limits are a concern:
function mirrorTree(root) { if (!root) return null; const queue = [root]; while (queue.length > 0) { const node = queue.shift(); // Swap children [node.left, node.right] = [node.right, node.left]; if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } return root; }
Iteratively, find the Max Element in a Give Binary Tree
Write a method findMaxItr to find the maximum element in a binary tree iteratively.
๐ช Stepper Hints
๐ Related LeetCode Problems
๐น Hint 1: How do we usually explore a tree?
To explore each node, we can use:
* Breadth-first traversal (BFS) using a queue, or
* Depth-first traversal (DFS) using a stack
Here, BFS is a great fit since we are only looking for a max value.
๐น Hint 2: What do we need to track?
As you visit nodes, track the maximum value found so far by comparing:max = Math.max(max, currentNode.data)
๐น Hint 3: When do we stop?
Once the queue is empty, weโve visited all nodes and found the maximum.
โ JavaScript Solution (BFS Iterative)
function findMaxItr(root) { if (root === null) return -Infinity; // or throw an error if empty tree let max = root.data; const queue = [root]; while (queue.length > 0) { const current = queue.shift(); max = Math.max(max, current.data); if (current.left) queue.push(current.left); if (current.right) queue.push(current.right); } return max; }
โฑ Time and Space Complexity
Time Complexity: O(n) โ every node is visited once
Space Complexity:
In the worst case, queue holds O(n) nodes (e.g., bottom level of a full binary tree)
๐ Alternate Approach (DFS Iterative)
function findMaxItr(root) { if (root === null) return -Infinity; let max = root.data; const stack = [root]; while (stack.length > 0) { const node = stack.pop(); max = Math.max(max, node.data); if (node.right) stack.push(node.right); if (node.left) stack.push(node.left); } return max; }
This DFS approach uses a stack instead of a queue.
Number of Full Nodes in a Binary Tree
Write a function to iteratively determine the total number of โfull nodesโ in a binary tree. A full node contains left and right child nodes. If there are no full nodes, return 0.
๐ช Stepper Hints
๐ Related LeetCode Problems
๐น Hint 1: What is a Full Node?
A full node has two children:if (node.left !== null && node.right !== null)
๐น Hint 2: How should we explore the tree?
To visit every node iteratively:
* Use BFS (with a queue), or
* Use DFS (with a stack)
Weโll use BFS since itโs intuitive and easy to implement.
๐น Hint 3: What to Track?
As you dequeue a node:
* Check if it is a full node.
* If yes, increment a counter.
* Continue exploring its children.
โ JavaScript Solution
function countFullNodes(root) { if (root === null) return 0; let count = 0; const queue = [root]; while (queue.length > 0) { const current = queue.shift(); if (current.left && current.right) { count++; } if (current.left) queue.push(current.left); if (current.right) queue.push(current.right); } return count; }
๐ Alternate Approach: DFS Iterative
function countFullNodes(root) { if (!root) return 0; let count = 0; const stack = [root]; while (stack.length > 0) { const node = stack.pop(); if (node.left && node.right) { count++; } if (node.right) stack.push(node.right); if (node.left) stack.push(node.left); } return count; }
โฑ Time and Space Complexity
Time Complexity: O(n) โ each node is visited once.
Space Complexity:
O(n) in the worst case (due to queue or stack usage)
Insert a Node at the Specified Position in Doubly Linked List
In doubly linked list, implement a method to insert a node at specified position and return the listโs head. Do nothing if insertion position is outside the bounds of the list.
๐ช Stepper Hints
๐ Related LeetCode Problems
๐น Hint 1: Whatโs a Doubly Linked List?
Each node has:
* data
* prev pointer
* next pointer
๐น Hint 2: Handle Special Cases First
* Position 0 โ Insert at head.
* Empty list โ Only insert if position is 0.
๐น Hint 3: Traverse to the Target Position
* Use a loop to move to the (position - 1)th node.
* Then adjust pointers to insert the new node between current and next nodes.
๐น Hint 4: Be Careful With Pointers
For the new node:
* newNode.next = current.next
* newNode.prev = current
* If current.next exists, update current.next.prev = newNode
* Then current.next = newNode
โ JavaScript Solution
class DoublyListNode { constructor(data) { this.data = data; this.prev = null; this.next = null; } } function insertAtPosition(head, data, position) { if (position < 0) return head; const newNode = new DoublyListNode(data); if (position === 0) { newNode.next = head; if (head) head.prev = newNode; return newNode; // New head } let current = head; let index = 0; while (current !== null && index < position - 1) { current = current.next; index++; } if (current === null) return head; // Position out of bounds newNode.next = current.next; newNode.prev = current; if (current.next !== null) { current.next.prev = newNode; } current.next = newNode; return head; }
โฑ Time and Space Complexity
Time Complexity: O(n) โ in worst case, we traverse the list to position n.
Space Complexity: O(1) โ constant space (in-place update).
๐ก Alternate Approach
You could convert this to a recursive insertion, though itโs less common for doubly linked lists.
Use a dummy node to simplify edge-case handling (e.g., empty list).
Find the Diameter of a BST
Given a BST, write a function to return its diameter. The diameter of a Binary Tree is defined as the โNumber of nodes on the longest path between two leaf nodesโ.
๐ช Stepper Hints
https://leetcode.com/problems/diameter-of-binary-tree/
๐ Related LeetCode Problems
๐น Hint 1: What is Diameter?
Think of the diameter as the longest path from one leaf to another in the tree.
๐น Hint 2: You Need to Check All Nodes
* At every node, check if the longest path through that node (from left leaf to right leaf) is the biggest so far.
* That path = leftHeight + rightHeight + 1 (counting current node too).
๐น Hint 3: What Info Do You Need?
* You need a function to calculate height of each subtree.
* But also, as you do this, keep updating the maximum diameter found.
๐น Hint 4: Recursive Depth First Search (DFS)
Use post-order traversal:
* Recursively find leftHeight and rightHeight
* Calculate potential diameter at this node
* Update a maxDiameter variable
* Return height of the current node = 1 + max(leftHeight, rightHeight)
โ JavaScript Solution
class TreeNode { constructor(data) { this.data = data; this.left = null; this.right = null; } } function diameterOfBST(root) { let maxDiameter = 0; function height(node) { if (!node) return 0; const leftHeight = height(node.left); const rightHeight = height(node.right); // Update diameter (number of nodes) const currentDiameter = leftHeight + rightHeight + 1; maxDiameter = Math.max(maxDiameter, currentDiameter); return 1 + Math.max(leftHeight, rightHeight); } height(root); return maxDiameter; }
โฑ Time & Space Complexity
Time Complexity: O(n) โ Every node is visited once.
Space Complexity: O(h) โ Recursive call stack; worst-case O(n) if tree is skewed.
๐ Alternate Approach
If you want to return edges instead of nodes, subtract 1 from the result:
diameter = maxDiameter - 1
Find the Maximum BST Node
Given a Binary Search Tree, write a method findMax that returns the node with the maximum data.
๐ช Stepper Hints
๐ Related LeetCode Problems
๐น Hint 1: Whatโs Special About a BST?
In a Binary Search Tree, the right child is greater than the current node.
So, the maximum value is always found by going to the rightmost node.
๐น Hint 2: What Would You Do If You Were Traversing the Tree?
Start from the root, then:
* While thereโs a right child, keep going right.
* The node with no right child is the maximum.
๐น Hint 3: Can You Solve This Iteratively?
Yes! Just use a loop:
* Initialize a pointer at the root.
* Keep updating the pointer to current.right until current.right === null.
๐น Hint 4: How About Recursively?
Yes, a recursive version would look for root.right until itโs null.
โ JavaScript Implementation
class TreeNode { constructor(data) { this.data = data; this.left = null; this.right = null; } } // Iterative version function findMax(root) { if (!root) return null; let current = root; while (current.right !== null) { current = current.right; } return current; }
โฑ Time & Space Complexity
โ
Time Complexity:
O(h), where h is the height of the tree.
Worst case: O(n) (skewed tree)
Best case: O(log n) (balanced tree)
โ
Space Complexity:
Iterative: O(1)
Recursive version: O(h) (call stack)
๐ Alternate Approaches
1. Recursive Version:
function findMaxRecursive(node) { if (!node || !node.right) return node; return findMaxRecursive(node.right); }