Review
- Recursion - 5*
- DFS - 5
- BFS - 5
- Dynamic Programming - 3
- Tree, Trie - 4
- Stack, Queue 4
What we have covered
- Heap 4
- HashMap - 4
- Sort - 3.5
- Bit Manipulation - 1
- Graph & Topology Sort - 4
- Union Find - 3
- Binary Search - 3.5
- Two Pointers - 3.5
- OO & System Design
Recursion & DFS
Find Sub-problems
Is it || or &&?
Watch out for the edge case and exit
Is Recursion the best way?
import java.util.HashSet;
import java.util.Set;
public class DFSExample {
// Recursive DFS template
public void dfs(Node node, Set<Node> visited) {
// Base case: return if node is already visited
if (visited.contains(node)) {
return;
}
// Mark the current node as visited
visited.add(node);
// Process the node (optional)
System.out.println("Visited: " + node.value);
// Recurse for all neighbors
for (Node neighbor : node.neighbors) {
dfs(neighbor, visited);
}
}
}
class DFSExample:
def dfs(self, node, visited=set()):
# Base case: return if node is already visited
if node in visited:
return
# Mark the node as visited
visited.add(node)
# Process the node (optional)
print("Visited:", node.value)
# Recursively visit all neighbors
for neighbor in node.neighbors:
self.dfs(neighbor, visited)
BFS
When to push items into queue?
Avoid null into queue
DFS for helping backtracking results
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> results = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
List<Integer> oneResult = new ArrayList<>();
Queue<TreeNode> queue2 = new LinkedList<>();
while (!queue.isEmpty()) {
TreeNode top = queue.poll();
if (top.left != null) {
queue2.offer(top.left);
}
if (top.right != null) {
queue2.offer(top.right);
}
oneResult.add(top.val);
}
results.add(oneResult);
queue = queue2;
}
return results;
}
def levelOrder(root):
results = []
queue = []
if root is not None:
queue.append(root)
while len(queue) > 0:
oneResult = []
queue2 = []
while len(queue) > 0:
top = queue.pop(0)
if top.left is not None:
queue2.append(top.left)
if top.right is not None:
queue2.append(top.right)
oneResult.append(top.val)
results.append(oneResult)
queue = queue2
return results
Dynamic Programming
What is the sub-optimal structure?
Deduction equation
Space complexity
public class FibonacciTabulation {
public int fibonacci(int n) {
// Handle base cases
if (n <= 1) {
return n;
}
// Initialize base values in the table
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
// Fill the table from the bottom up
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
class FibonacciTabulation:
def fibonacci(self, n):
# Handle base cases
if n <= 1:
return n
# Initialize base values in the table
dp = [0] * (n + 1)
dp[1] = 1
# Fill the table from the bottom up
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
Tree & Trie
How to traverse
Remember the structure properties
Go with BFS/DFS
BST -> Balanced BST
public void preorder(TreeNode root) {
if(root != null) {
//Visit the node by Printing the node data
System.out.printf("%c ",root.data);
preorder(root.left);
preorder(root.right);
}
}
def preorder(root):
if root is not None:
# Visit the node by printing the node data
print(root.data, end=" ")
preorder(root.left)
preorder(root.right)
public void inorder(TreeNode root) {
if(root != null) {
inorder(root.left);
System.out.printf("%c ",root.data);
inorder(root.right);
}
}
def inorder(root):
if root is not None:
inorder(root.left)
print(root.data, end=" ")
inorder(root.right)
public void postorder(TreeNode root) {
if(root != null) {
postorder(root.left);
postorder(root.right);
System.out.printf("%c ",root.data);
}
}
def postorder(root):
if root is not None:
postorder(root.left)
postorder(root.right)
print(root.data, end=" ")
public class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (stack.size() > 0 || root != null) {
while (root != null) {
stack.push(root); // first time
res.add(root.val);
root = root.left;
}
if (!stack.isEmpty()) {
TreeNode top = stack.pop();
if (top.right != null) {
root = top.right;
}
}
}
return res;
}
}
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
stack, res = [], []
while stack or root:
while root:
stack.append(root) # first time
res.append(root.val)
root = root.left
if stack:
top = stack.pop()
if top.right:
root = top.right
return res
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
if (!stack.isEmpty()) {
root = stack.pop();
res.add(root.val);
root = root.right;
}
}
return res;
}
def inorderTraversal(self, root: TreeNode) -> List[int]:
stack = []
res = []
while root or stack:
while root:
stack.append(root)
root = root.left
if stack:
root = stack.pop()
res.append(root.val)
root = root.right
return res
Binary Search Tree
public static boolean find(int value, Node root) {
Node node = root;
while(node != null) {
if(node.data > value) {
node = node.leftNode;
}
else if(node.data < value) {
node = node.rightNode;
}
else return true;
}
return false;
}
def find(value, root):
node = root
while node is not None:
if node.data > value:
node = node.leftNode
elif node.data < value:
node = node.rightNode
else:
return True
return False
Stack & Queue
Increasing & Decreasing Stack
Sometimes you need to push index
public int largestRectangleArea(int[] height) {
int area = 0;
java.util.Stack<Integer> stack = new java.util.Stack<Integer>();
for (int i = 0; i < height.length; i++) {
if (stack.empty() || height[stack.peek()] < height[i]) {
stack.push(i);
}
else {
int start = stack.pop();
int width = stack.empty() ? i : i - stack.peek() - 1;
area = Math.max(area, height[start] * width);
i--;
}
}
while (!stack.empty()) {
int start = stack.pop();
int width = stack.empty() ? height.length : height.length - stack.peek() - 1;
area = Math.max(area, height[start] * width);
}
return area;
}
def largestRectangleArea(self, height: List[int]) -> int:
area = 0
stack = []
height.append(0)
for i in range(len(height)):
while stack and height[i] < height[stack[-1]]:
h = height[stack.pop()]
width = i if not stack else i - stack[-1] - 1
area = max(area, h * width)
stack.append(i)
return area
Heap
When you do not care about the inside details
Comparator and Comparable
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0)
return null;
Comparator<ListNode> comparator = new Comparator<ListNode> () {
public int compare(ListNode node1, ListNode node2) {
return node1.val - node2.val;
}
};
PriorityQueue<ListNode> minHeap =
new PriorityQueue<ListNode>(lists.length, comparator);
for (int i = 0; i < lists.length; i++) {
if (lists[i] != null) {
minHeap.add(lists[i]);
}
}
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (!minHeap.isEmpty()) {
cur.next = minHeap.poll();
cur = cur.next;
if (cur.next != null)
minHeap.add(cur.next);
}
return dummy.next;
}
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
q = []
for index, each_list in enumerate(lists):
if each_list:
heapq.heappush(q, [each_list.val, index, each_list])
ans = []
dummy = ListNode(-1)
cur = dummy
while q:
v, i, node = heapq.heappop(q)
cur.next = node
cur = cur.next
if node.next:
heapq.heappush(q, [node.next.val, i, node.next])
return dummy.next
HashMap
Hash Function & Collision
HashSet? TreeSet?
HashMap + DFS/ArrayList?
idea of Hashing
class Solution {
public int subarraySum(int[] nums, int k) {
int count = 0;
int sum = 0;
Map<Integer, Integer> map = new HashMap();
map.put(0, 1);
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (map.containsKey(sum - k)) {
count += map.get(sum - k);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return count;
}
}
def subarraySum(self, nums, k):
count = 0
sum_val = 0
hashmap = {0: 1}
for num in nums:
sum_val += num
if sum_val - k in hashmap:
count += hashmap[sum_val - k]
hashmap[sum_val] = hashmap.get(sum_val, 0) + 1
return count
Sort
Remember the similarity and difference
Bucket Sort
In-place Sort
Collections.sort and Arrays.sort
Graph & Topology Sort
DAG
BFS/DFS
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] degrees = new int[numCourses]; // how many courses need to finish if we want to take the course
List<List<Integer>> pre = new ArrayList<>(); // I am who's the prerequisites
// Initialize the pre list
for (int i = 0; i < numCourses; i++) {
pre.add(new ArrayList<>());
}
// Populate degrees and pre lists
for (int[] pair : prerequisites) { // O(E)
int course = pair[0];
int preCourse = pair[1];
degrees[course]++;
pre.get(preCourse).add(course);
}
List<Integer> cur = new ArrayList<>();
for (int i = 0; i < numCourses; i++) { // O(V)
if (degrees[i] == 0) { // the courses doesn't need prerequisites
cur.add(i);
}
}
List<Integer> ans = new ArrayList<>();
while (!cur.isEmpty()) { // O(V + E)
List<Integer> next = new ArrayList<>();
for (int i : cur) {
for (int x : pre.get(i)) {
degrees[x]--;
if (degrees[x] == 0) {
next.add(x);
}
}
}
ans.addAll(cur);
cur = next;
}
return ans.size() == numCourses;
}
Course Schedule
Course Schedule
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
degrees = [0 for _ in range(numCourses)] # how many courses need to finish if we want to take the course
pre = [[] for _ in range(numCourses)] # I am who's the prerequisites
for i, j in prerequisites: # O(E)
degrees[i] += 1
pre[j].append(i)
cur = []
for i in range(numCourses): # O(V)
if degrees[i] == 0: # the courses doesn't need to prerequisites
cur.append(i)
ans = []
while (cur): # O(V + E)
next = []
for i in cur:
for x in pre[i]:
degrees[x] -= 1
if degrees[x] == 0:
next.append(x)
ans += cur
cur = next
return len(ans) == numCourses
# TC, SC: O(V+E)
Union Find
A different kind of tree
Help to count number of sets
class UnionFind {
List<Integer> list;
public UnionFind(int n) {
list = new ArrayList<>();
for (int i = 0; i < n; i ++) {
list.add(i);
}
}
boolean union(int a, int b) {
int ancesterA = find(a), ancesterB = find(b);
if (ancesterA == ancesterB) return false; // need not to union.
else {
list.set(ancesterB, ancesterA);
return true;
}
}
int find(int k) {
int i = k;
while (i != list.get(i)) {
i = list.get(i); //Here i is the root.
}
return i;
}
}
class UnionFind:
def __init__(self, n):
self.list = [i for i in range(n)]
def union(self, a, b):
ancestor_a = self.find(a)
ancestor_b = self.find(b)
if ancestor_a == ancestor_b:
return False
else:
self.list[ancestor_b] = ancestor_a
return True
def find(self, k):
i = k
while i != self.list[i]:
i = self.list[i] # Here i is the root.
return i
Bit Manipulation
Small tricks
XOR
Single Number
OO & System Design
- Clarify the question
- Start with main objects
- Try link them up
- Action with examples
- Accuracy
- Performance
- Robustness
Copy of [Fred] Review
By ZhiTongGuiGu
Copy of [Fred] Review
- 39