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?

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

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

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

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)
def inorder(root):
    if root is not None:
        inorder(root.left)
        print(root.data, end=" ")
        inorder(root.right)
def postorder(root):
    if root is not None:
        postorder(root.left)
        postorder(root.right)
        print(root.data, end=" ")
    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 
  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

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

    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

    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

    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

Merge Sort/ Quick Sort

In-place Sort

Collections.sort and Arrays.sort

Graph & Topology Sort

DAG

BFS/DFS

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:
    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

Python [Jo] Review 16

By ZhiTongGuiGu

Python [Jo] Review 16

  • 72