Simple max-aggregation DP algorithm for calculating the longest path in a DAG. Some cool engineering features:
dp[u] accordingly if another starting spot provides a longer path and we take a max over all returned values. Aggregating over all returned values of a recursive DP function is a really common way of consolidating the final answer.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))
Intuition: Why DP?
Intuition: What to notice
Main Explanation
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]
Intuition
Main Idea
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