Dynamic Programming Conceptual

Dynamic Programming Toolkit and Tips

Longest Path Dag

Simple max-aggregation DP algorithm for calculating the longest path in a DAG. Some cool engineering features:

def longest_path_dag(adjList):
    n = len(adjList)
    dp = [-1] * n  # dp[u] = length of longest path starting from u

    def dfs(u):
        if dp[u] != -1:
            return dp[u]
        max_len = 0

        # dag means we will hit a non-cyclic leaf
        # for loop doesn't execute and we just store max length = 0

        # then for non-leaf cases we compare the lengths of all neighbors 
        # add + 1 to account for current node
        for v in adjList[u]: 
            max_len = max(max_len, 1 + dfs(v))
        dp[u] = max_len
        return dp[u]

    return max(dfs(u) for u in range(n))

Dynamic Programming Leetcode Classics

Climbing Up Stairs

Problem

Main Idea

Intuition: Why DP?

Intuition: What to notice

Main Explanation

Implemenatations

Naive (recursive no brute force - backtracking)

def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 1:
            return 1
        elif n == 2:
            return 2


        n -= 1
        ones = self.climbStairs(n) # take one step
        n -= 1
        twos = self.climbStairs(n) # take two steps
        return ones + twos

Top Down Recursive Memoization

class Solution(object):
        def climbStairs(self, n):
            memo = {}


            def dfs(steps):
                if steps == 0:
                    return 1  # One way to do nothing
                if steps < 0:
                    return 0  # No way to go negative
                if steps in memo:
                    return memo[steps]


                # Try taking 1 or 2 steps and cache the result
                memo[steps] = dfs(steps - 1) + dfs(steps - 2)
                return memo[steps]


            return dfs(n)

Bottom-Up Iterative Tabular

class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 2:
            return n
        dp = [0] * (n + 1)
        dp[1], dp[2] = 1, 2
        for i in range(3, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]
        return dp[n]

Dynamic Programming Leetcode Nifty

Range Sum Query 2D

Main Idea

Intuition

Main Idea

Implementation

from typing import List

class NumMatrix:


    def __init__(self, matrix: List[List[int]]):
        """
        DP prefixsum problem where you sum along the axis then subtract
        """
        # [row][col] indexing setup, we do + 1 to pad the matrix
        prefix_matrix = [ [0] * (len(matrix[0]) + 1) for i in range(len(matrix) + 1)]
        print("dim of prefix matrix", len(prefix_matrix), len(prefix_matrix[0]))


        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                print("coords", (i + 1, j + 1))
                curr = matrix[i][j] # base matrix[i][j] corresponds to prefix[i + 1][j + 1]
                dp_prev = prefix_matrix[i][j]
                dp_top = prefix_matrix[i][j + 1]
                dp_left = prefix_matrix[i + 1][j]


                prefix_matrix[i + 1][j + 1] = curr + dp_top + dp_left - dp_prev


        self.pref = prefix_matrix


    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        left = self.pref[row2 + 1][col1]
        top = self.pref[row1][col2 + 1]
        topleft = self.pref[row1][col1]


        return self.pref[row2 + 1][col2 + 1] + topleft - left - top