11 minute read

Basic Study Plan

Climbing Stairs

Recursive Formula

$N[n] = N[n-1] + N[n-2], n> 2$

$N[0] = 0, N[1] = 1, N[2] = 2$

Python3

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

Fibonacci Number

Recursive Formula

$F[n] = F[n-1] + F[n-2], n > 1$

$F[0] = 0, F[1] = 1$

Python3

class Solution:
    def climbStairs(self, n: int) -> int:
        if n == 1 or n == 2:
            return n
        # store result for reusage
        count = [0] * (n+1)
        count[1], count[2] = 1, 2 
        # climb submodule
        def climb(n):
            if not count[n]:
                count[n] = climb(n-1) + climb(n-2)
            return count[n]
        return climb(n)

N-th Tribonacci Number

Recursive Formula

$F[n] = F[n-1] + F[n-2], n \ge 2$

$F[0] = 0, F[1] = 1$

Python3

class Solution:
    def climbStairs(self, n: int) -> int:
        if n == 1 or n == 2:
            return n
        # store result for reusage
        count = [0] * (n+1)
        count[1], count[2] = 1, 2 
        # climb submodule
        def climb(n):
            if not count[n]:
                count[n] = climb(n-1) + climb(n-2)
            return count[n]
        return climb(n)

Min Cost Climbing Stairs

Recursive Formula

$OPT[i] = \min{OPT[i-1] +cost[i-1], OPT[i-2] +cost[i-2]}, i \ge 2$​

$OPT[0] = OPT[1] = 0$

Python3

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        n = len(cost)
        opt = [0] * (n+1)
        for i in range(2, n+1):
            opt[i] = min(opt[i-1] + cost[i-1], opt[i-2] + cost[i-2])
        return opt[n]  
        

House Robber

Recursive Formula

\[OPT[i] = \max \cases{ \displaystyle \max_{0\le j \le i-2}\{OPT[j] + nums[i]\}, \text{rob $i$-th house}\\ \displaystyle \max_{0 \le j \le i -1}\{OPT[j]\}, \text{Not rob $i$-th house} }, 0\le i < n\]

Python3

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        opt = [0] * (n)
        opt[0] = nums[0]
        for i in range(1, n):
            # rob i-th house
            max_rob = max(opt[:i-1]) + nums[i] if i > 1 else nums[i]
            # not rob i-th house & update 
            opt[i] = max(max(opt[:i]), max_rob)
        return opt[n-1]

Delete and Earn

Recursive Formula

Denote $V$ as sorted values in $nums$, $n = |V|$, $OPT[i]$ denotes the max score for the subset with $V[i-1]$ as maximum \(OPT[i] = \max \cases{ \displaystyle \cases{ OPT[i-2]+ V[i-1] * \# V[i-1], \text{if $V[i-1] = V[i-2]+1$}\\ OPT[i-1]+ V[i-1] * \# V[i-1], \text{if $V[i-1] \neq V[i-2]+1$} }, \text{delete $V[i-1]$}\\ \displaystyle OPT[i-1], \text{Not delete $V[i-1]$} }, 0< i \le n\)

$OPT[0] = 0$

Python3

class Solution:
    def deleteAndEarn(self, nums: List[int]) -> int:
        counter = Counter(nums)
        vals = sorted(counter.keys())
        opt = [0] * (len(vals) + 1)
        for i in range(1, len(vals)+1):
            score = vals[i-1] * counter[vals[i-1]]
            if vals[i-1] != vals[i-2]+1:
                opt[i] = opt[i-1] + score
            else:
                opt[i] = max(opt[i-1], opt[i-2] + score)
        return opt[-1]

Unique Paths

Recursive Formula

$grid[i][j] = grid[i][j-1] + grid[i-1][j], 0<i<m, 0<j<n$​

$grid[i][0] = 1, grid[0][j] = 1, 0<i<m, 0<j<n$​

Fill order: row by row / column by column

Notice: initialization of matrix

Python3

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        grid = [[1 for _ in range(n)] for _ in range(m)]
        for i in range(1, m):
            for j in range(1, n):
                grid[i][j] = grid[i-1][j] + grid[i][j-1]
        return grid[m-1][n-1]

Minimum Path Sum

Recursive Formula

$OPT[i][j] = \min {OPT[i-1][j], OPT[i][j-1]} + grid[i][j], 0<i<m, 0<j<n$

$OPT[0][0] = grid[0][0]$

$OPT[i][0] = OPT[i-1][0] + grid[i][0], 0<i<m$

$OPT[0][j] = OPT[0][j-1] + grid[0][j], 0<j<n$

Python3

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        opt = [[grid[0][0] for _ in range(n)] for _ in range(m)]
        for i in range(1, m):
            opt[i][0] = opt[i-1][0] + grid[i][0]
        for j in range(1, n):
            opt[0][j] = opt[0][j-1] + grid[0][j]
        for i in range(1, m):
            for j in range(1, n):
                opt[i][j] = min(opt[i][j-1], opt[i-1][j]) + grid[i][j]
        return opt[m-1][n-1] 
        

Unique Paths II

Recursive Formula

$OPT[i][j] = OPT[i-1][j] * (1-obstacle[i-1][j]) + OPT[i][j-1] * (1-obstacle[i][j-1]), 0<i<m, 0<j<n$

Notice:Boundary Condition (Consider the realistic scenario!!!)

For first row and column, possible path become 0 once an obstacle occurs in the row/column

Python3

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        opt = [[0 for _ in range(n)] for _ in range(m)]
        for i in range(m):
            if obstacleGrid[i][0]:
                break
            opt[i][0] = 1
        for j in range(n):
            if obstacleGrid[0][j]:
                break
            opt[0][j] = 1
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j]:
                    opt[i][j] = 0
                else:
                    opt[i][j] = opt[i-1][j] * (1-obstacleGrid[i-1][j]) + \
                            opt[i][j-1] * (1-obstacleGrid[i][j-1])
        return opt[m-1][n-1]

Triangle

Recursive Formula

$OPT[i][j]$ denotes mininal sum at $j$-th location of $i$​-th layer

$OPT[i][j] = \min {OPT[i-1][\min(i-1, j)], OPT[i-1][max(0, j-1)]} + V[i][j], 0<i<n, 0\le j\le i$

$OPT[0][0] = V[0][0]$

Python3

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        prev = triangle[0]
        n = len(triangle)
        if n == 1:
            return triangle[0][0]
        for i in range(1, n):
            count = len(triangle[i])
            cur = [0] * count
            for j in range(count):
                cur[j] = min(prev[max(0, j-1)], prev[min(i-1, j)]) + \
                           triangle[i][j]
            prev = cur
        return min(cur)

Minimum Falling Path Sum

Recursive Formula

$OPT[i][j] = \min{OPT[i-1][j-1], OPT[i-1][j], OPT[i][j-1]} + M[i][j], 0\le i, j < n$​

$OPT[-1][j] = OPT[i][-1] = OPT[n][j] = OPT[i][n] = \infty$

Python3

class Solution:
    def minFallingPathSum(self, matrix: List[List[int]]) -> int:
        n = len(matrix)
        if n == 1:
            return matrix[0][0]
        prev = matrix[0]
        max_int = 2 ** 31 - 1
        for i in range(1, n):
            cur = [0] * n
            for j in range(n):
                val1 = prev[j-1] if j > 0 else max_int
                val2 = prev[j]
                val3 = prev[j+1] if j < n-1 else max_int
                cur[j] = min(val1, val2, val3) + matrix[i][j]
            prev = cur
        return min(cur)
      

Maximal Square

Recursive Formula

\[OPT[i][j] = \cases{ 0, M[0][0] == 0\\ \cases{ \min\{OPT[i-1][j], OPT[i][j-1]\} + 1, OPT[i-1][j] == OPT[i][j-1] \\ OPT[i-1][j] + I(OPT[i-1][j-1] \ge OPT[i-1][j]) }, M[0][0] == 1 } 0 < i < m, 0 < j < n\]

$OPT[i][0] = M[i][0], 0 < i < m$​

$OPT[0][j] = M[0][j], 0 < j < n$

Python3

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        m, n = len(matrix), len(matrix[0])
        opt = [[int(matrix[i][j]) for j in range(n)] for i in range(m)]
        result = int(max(matrix[0]))
        for i in range(1, m):
            result = max(int(matrix[i][0]), result)
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][j] == "1":
                    val1, val2 = opt[i-1][j], opt[i][j-1]
                    if val1 == val2:
                        opt[i][j] = val1 + 1 if opt[i-1][j-1] >= val1 else val1
                    else:
                        opt[i][j] = min(val1, val2) + 1
                    result = max(result, opt[i][j])
        return result ** 2

Word Break

Recursive Formula

$DP[i]$ denotes whether $s[:i+1]$ could be break into words in dictionary

  • $\exists j < i, DP[j] == true \text{ and } s[j+1:i+1] \in dict \Rightarrow DP[i] = true$​
  • $s[:i+1] \in dict \Rightarrow DP[i] = true$​

Python3

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        n = len(s)
        dp = [False] * n
        dp[0] = True if s[0] in wordDict else False
        for i in range(1, n):
            j = i-1
            while j >= 0:
                if dp[j]:
                    if s[j + 1:i + 1] in wordDict:
                        dp[i] = True
                        break
                j -= 1
            dp[i] = s[:i+1] in wordDict or dp[i]
        return dp[-1]

Longest Palindromic Subsequence

Recursive Formula

$DP[i][j]$ denotes the length of longest palindromic subsequence in $s[i:j+1]$ \(DP[i][j] = \cases{ 1, i == j\\ DP[i+1][j] + 2, \text{if } s[i] == s[j]\\ \max\{DP[i+1][j], DP[i][j-1]\}, \text{otherwise} }, 0 \le i \le j < n\)

Python3

class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        dp = [[0 for _ in range(n)] for _ in range(n)]
        for i in range(n-1, -1, -1):
            dp[i][i] = 1
            for j in range(i+1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i+1][j-1] + 2
                else:
                    dp[i][j] = max(dp[i+1][j], dp[i][j-1])
        return dp[0][-1]

Edit Distance

Recursive Formula

$DP[i][j]$ denotes the min edit distance of $word_1[:i+1]$ and $word_2[:j+1]$​​

$\text{For } 0<i<|word_1|, 0<j<|word_2|$ $ \(DP[i][j] = \cases{ DP[i-1][j-1], \text{if } word_1[i] == word_2[j]\\ \min\{DP[i-1][j-1], DP[i][j-1], DP[i-1][j]\} + 1, \text{if } word_1[i] \neq word_2[j] } \\ DP[i][0] = \cases{ i+1 \text{ if } word_2[0] \notin word_1[:i+1] \\ i, \text{otherwise} }\\ DP[0][j] = \cases{ j+1 \text{ if } word_1[0] \notin word_2[:j+1] \\ j, \text{otherwise} }\)

Python3

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        len1, len2 = len(word1), len(word2)
        # boundary case
        if not len1 or not len2:
            return max(len1, len2)
        dp = [[0 for _ in range(len2)] for _ in range(len1)]
        dp[0][0] = 0 if word1[0] == word2[0] else 1
        # initialize first column
        flag = 1 if word1[0] == word2[0] else 0
        for i in range(1, len1):
            if word1[i] == word2[0]:
                flag = 1
            dp[i][0] = i + 1 - flag
        # initialize first row
        flag = 1 if word1[0] == word2[0] else 0
        for j in range(1, len2):
            if word1[0] == word2[j]:
                flag = 1
            dp[0][j] = j + 1 - flag
        # dynamic programming
        for i in range(1, len1):
            for j in range(1, len2):
                if word1[i] == word2[j]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
        return dp[-1][-1]

Minimum ASCII Delete Sum for Two Strings

Recursive Formula

$OPT[i][j] = \cases{\min{OPT[i-1][j] + ascii(s_1[i-1]), OPT[i][j-1] + ascii(s_2[j-1])}, \text{if } s_1[i-1] \neq s_2[j-1] \ OPT[i-1][j-1], \text{if } s_1[i-1] == s_2[j-1]}, 1\le i \le s_1 , 1\le j \le s_2 $
$OPT[i][0] = OPT[i-1][0] + ascii(s_1[i-1]), 1\le i\le s_1 $​
$OPT[0][j] = OPT[0][j-1] + ascii(s_2[j-1]), 1\le j\le s_2 $

Python3

class Solution:
    def minimumDeleteSum(self, s1: str, s2: str) -> int:
        l1, l2 = len(s1), len(s2)
        dp = [[0 for _ in range(l2+1)] for _ in range(l1+1)]
        for i in range(1, l1+1):
            dp[i][0] = dp[i-1][0] + ord(s1[i-1])
        for j in range(1, l2+1):
            dp[0][j] = dp[0][j-1] + ord(s2[j-1])
        for i in range(1, l1+1):
            for j in range(1, l2+1):
                if s1[i-1] == s2[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = min(dp[i-1][j] + ord(s1[i-1]), dp[i][j-1] + ord(s2[j-1]))
        return dp[l1][l2]

Distinct Subsequences

Recursive Formula

$OPT[i][j]$ denotes the count of subsequences in $s[:j+1]$ matching $t[:i+1]$​

When one letter matches, you could choose either including it in subsequence or not.

$OPT[i][j] = \cases{OPT[i][j-1] + OPT[i-1][j-1], \text{if } s[i-1] == t[j-1]\ OPT[i][j-1], \text{otherwise}}, 1\le i\le t , 1 \le j \le s $​
$OPT[i][0] = 0, OPT[0][j] = 1, 1\le i\le t , 0 \le j \le s $

Python3

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        l1, l2 = len(t), len(s)
        dp = [[0 for _ in range(l2+1)] for _ in range(l1+1)]
        for j in range(1, l2+1):
            dp[0][j] = 1
        dp[0][0] = 1
        for i in range(1, l1+1):
            for j in range(1, l2+1):
                if t[i-1] == s[j-1]:
                    dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
                else:
                    dp[i][j] = dp[i][j-1]
        return dp[l1][l2]

Longest Increasing Subsequence

Recursive Formula

$OPT[i]$ demostrates the LIS ending at $i$-th index \(OPT[i] = \max_{0 \le j < i}\cases{ OPT[j]+1, \text{if }nums[i] > nums[j] \\ 1, \text{otherwise } }, 0 < i < n\) Boundary: $OPT[0] = 1$​

Output: $\max{OPT[i]}, 0 \le i < n$

Python3

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [1] * n
        for i in range(1, n):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[j] + 1, dp[i])
        return max(dp)
        

Number Of Longest Increasing Subsequences

Recursive Formula

$DP_1$ demonstrates the LIS ending at $i$-th index

$DP_2$ demonstrates the count of LIS ending at $i$-th index \(DP_2[i] = \max_{0 \le j < i}\cases{ \cases{ DP_2[i] + DP_2[j], \text{if } DP_1[j] + 1 = DP_1[i]\\ DP_2[j], \text{if } DP_1[j] + 1 > DP_1[i] }, \text{if }nums[i] > nums[j] \\ 1, \text{otherwise } }, 0 < i < n\) Boundary: $DP_2[0] = 1$

Output: $\displaystyle \sum^{n-1}_{i=0} DP_2[i]*I(DP_1[i] == \max DP_1)$

Python3

class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        dp1 = [1] * n
        dp2 = [1] * n
        for i in range(1, n):
            for j in range(i):
                if nums[i] > nums[j]:
                    # find prev seq
                    if dp1[i] == dp1[j] + 1:
                        dp2[i] += dp2[j]
                    # update LIS
                    elif dp1[i] < dp1[j] + 1:
                        dp2[i] = dp2[j]
                        dp1[i] = dp1[j] + 1
        M = max(dp1)
        result = 0
        for i in range(n):
            if dp1[i] == M: 
                result += dp2[i]
        return result

Longest Arithmetic Subsequence of Given Difference

Recursive Formula

Use hash table to store dp table, $DP[v]$ denotes the max length of arithmetic subsequence ends at $v$

$DP[v] = DP[v-d] + 1, v \in A$

$DP[v] = 0 \text{ if } v \notin A$

Python3

class Solution:
    def longestSubsequence(self, arr: List[int], difference: int) -> int:
        dp = defaultdict(int)
        for v in arr:
            dp[v] = dp[v-difference] + 1
        return max(dp.values())

####

Recursive Formula

Python3


Unique Binary Search Trees

Recursive Formula

$count[i, k]$ denotes $#(BST)$ forming with values from 1 to $k$ with $i$​ as root

$DP[k]$ denotes $#(BST)$ forming with values from 1 to $k$, $DP[k] = \displaystyle \sum_{i=1}^k count[i, k]$

$count[i, k] = DP[i-1] * DP[k-i]$​

$DP[k] = \displaystyle \sum_{i=1}^k DP[i-1] * DP[k-i], 0 < i \le k, 2 \le k \le n$

Boundary: $DP[0] = DP[1] = 1$

Output: $DP[n]$

Python3

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

Other

Jump Game II

Recursive Formula

Original Method:

Define $DP[i]$ as min step to reach $i$, $0\le i < L $

$DP[i] = \displaystyle \min_{k < i}{f(k): f(k) =\begin{cases}&DP[k]+1, \text{if}~nums[k] + k \ge i \ & \infty , \text{otherwise}\end{cases}}$

$DP[0] = 0$

Optimized Method:

Define $DP[i]$ as the farest index you can reach within one step from any index $k$ where $k\le i$

$DP[i] = \displaystyle \max_{k \le i}{DP[k], i + nums[i]}$

Given $DP[k] < DP[k+1]$, so $DP[i] = \displaystyle \max{DP[i-1], i + nums[i]}$

Python3

class Solution:
    def jump(self, nums: List[int]) -> int:
        l = len(nums)
        dp = [0 for _ in range(l)]
        for i in range(1, l):
            dp[i] = min([dp[k]+1 if nums[k] + k >= i else l for k in range(i)])
        return dp[-1]
        
class Solution:
    def jump(self, nums: List[int]) -> int:
        l = len(nums)
        dp = [nums[0] for _ in range(l)]
        for i in range(1, l):
            dp[i] = max(dp[i-1], nums[i]+i)
        step = next_idx = 0
        while next_idx < l - 1:
            next_idx = dp[next_idx]
            step += 1
        return step