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

  • 45