DP (Dynamic programming)
0) Concept
- Dynamic programming, like the divide-and-conquer method, solves
problems by combining the solutions to subproblems
- Dynamic: time-varying
- Programming: a tabular method
- space -> time 用空間換取時間
- trace progress 讓走過的留下痕跡
- Ref
- https://predoc.dlc.ntu.edu.tw/viewer?embedded=true&url=https%3A%2F%2Fcool.ntu.edu.tw%2Fcourses%2F8583%2Ffiles%2F1165602%2Fdownload%3Fverifier%3DnlJ3s1a9TTmgYQzbJgj9vnrGlKKZB4w0wUZyEKgm
0-1) Types
0-2) Pattern
1-1) Basic OP
2) LC Example
2-1) Unique Paths
// java
// LC 62
// V0
// IDEA: 2D DP (fixed by gpt)
public int uniquePaths(int m, int n) {
if (m == 0 || n == 0)
return 0;
int[][] dp = new int[m][n];
/** NOTE !!! init val as below
*
* -> First row and first column = 1 path
* (only one way to go right/down)
*/
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int j = 0; j < n; j++) {
dp[0][j] = 1;
}
// Fill the rest of the DP table
// NOTE !!! i, j both start from 1
// `(0, y), (x, 0)` already been initialized
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
/** DP equation
*
* dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
*/
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
# 62. Unique Paths
# V0
# IDEA : BFS + dp (memory)
class Solution:
def uniquePaths(self, m, n):
# NOTE !!! we init paths as below
paths = [[1]*n for _ in range(m)]
q = deque()
q.append((0,0))
while q:
row, col = q.popleft()
if row == m or col == n or paths[row][col] > 1:
continue
if row-1 >= 0 and col-1 >= 0:
paths[row][col] = paths[row-1][col] + paths[row][col-1]
q.append((row+1, col))
q.append((row, col+1))
return paths[-1][-1]
# V0'
# IDEA : DP
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
d = [[1] * n for _ in range(m)]
for col in range(1, m):
for row in range(1, n):
d[col][row] = d[col - 1][row] + d[col][row - 1]
return d[m - 1][n - 1]
2-2) Maximum Product Subarray
# NOTE : there is also brute force approach
# V0
# IDEA : brute force + product
class Solution(object):
def maxProduct(self, A):
global_max, local_max, local_min = float("-inf"), 1, 1
for x in A:
local_max = max(1, local_max)
if x > 0:
local_max, local_min = local_max * x, local_min * x
else:
local_max, local_min = local_min * x, local_max * x
global_max = max(global_max, local_max)
return global_max
# V1
# IDEA : BRUTE FORCE (TLE)
# https://leetcode.com/problems/maximum-product-subarray/solution/
class Solution:
def maxProduct(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
result = nums[0]
for i in range(len(nums)):
accu = 1
for j in range(i, len(nums)):
accu *= nums[j]
result = max(result, accu)
return result
# V1
# IDEA : DP
# https://leetcode.com/problems/maximum-product-subarray/solution/
# LC 152
class Solution:
def maxProduct(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
max_so_far = nums[0]
min_so_far = nums[0]
result = max_so_far
for i in range(1, len(nums)):
curr = nums[i]
temp_max = max(curr, max_so_far * curr, min_so_far * curr)
min_so_far = min(curr, max_so_far * curr, min_so_far * curr)
max_so_far = temp_max
result = max(max_so_far, result)
return result
// java
// LC 152
// V0
// IDEA : DP
// https://github.com/yennanliu/CS_basics/blob/master/leetcode_python/Dynamic_Programming/maximum-product-subarray.py#L69
// IDEA : cur max = max (cur, cur * dp[k-1])
// But, also needs to consider "minus number"
// -> e.g. (-1) * (-3) = 3
// -> so we NEED to track maxSoFar, and minSoFar
public int maxProduct(int[] nums) {
// null check
if (nums.length == 0){
return 0;
}
// init
int maxSoFar = nums[0];
int minSoFar = nums[0];
int res = maxSoFar;
for (int i = 1; i < nums.length; i++){
int cur = nums[i];
/**
* or, can use below trick to get max in 3 numbers
*
* max = Math.max(Math.max(max * nums[i], min * nums[i]), nums[i]);
* min = Math.min(Math.min(temp * nums[i], min * nums[i]), nums[i]);
*
*/
int tmpMax = findMax(cur, maxSoFar * cur, minSoFar * cur);
minSoFar = findMin(cur, maxSoFar * cur, minSoFar * cur);
maxSoFar = tmpMax;
res = Math.max(maxSoFar, res);
}
return res;
}
private int findMax(int a, int b, int c){
if (a >= b && a >= c){
return a;
}
else if (b >= a && b >= c){
return b;
}else{
return c;
}
}
private int findMin(int a, int b, int c){
if (a <= b && a <= c){
return a;
}
else if (b <= a && b <= c){
return b;
}else{
return c;
}
}
2-3) Best
Time to Buy and Sell Stock with Transaction Fee
// java
// LC 714
// V0-1
// IDEA: DP (gpt)
/**
* Solution Explanation:
*
*
* - Use two variables to represent the state:
* 1. hold: The maximum profit achievable
* while holding a stock at day i.
*
* 2. cash: The maximum profit achievable
* while not holding a stock at day i.
*
* - Transition equations:
* - If holding a stock:
* hold = max(hold, cash - price[i])
*
* NOTE: 2 cases we hold th stock: 1) already hold from previous day 2) buy a new stock today
* (`hold`: You already held the stock from a previous day -> If you decided not to make any changes today, then the profit remains the same as the previous hold.)
* (`cash - price[i]`: You buy the stock today -> To buy the stock today, you need to spend money, reducing your profit. The cost to buy the stock is prices[i]. However, the amount of money you can spend is the maximum profit you had when you were not holding a stock previously (cash).)
*
* (You either keep holding or buy a new stock.)
* - If not holding a stock:
* cash = max(cash, hold + price[i] - fee)
*
*
* (You either keep not holding or sell the stock and pay the fee.)
* - Initialize:
* - hold = -prices[0] (If you buy the stock on the first day).
* - cash = 0 (You haven’t made any transactions yet).
*
*/
/**
* Example Walkthrough:
*
* Input:
* • Prices: [1, 3, 2, 8, 4, 9]
* • Fee: 2
*
* Steps:
* 1. Day 0:
* • hold = -1 (Buy the stock at price 1).
* • cash = 0.
* 2. Day 1:
* • cash = max(0, -1 + 3 - 2) = 0 (No selling since profit is 0).
* • hold = max(-1, 0 - 3) = -1 (No buying since it’s already better to hold).
* 3. Day 2:
* • cash = max(0, -1 + 2 - 2) = 0.
* • hold = max(-1, 0 - 2) = -1.
* 4. Day 3:
* • cash = max(0, -1 + 8 - 2) = 5 (Sell at price 8).
* • hold = max(-1, 5 - 8) = -1.
* 5. Day 4:
* • cash = max(5, -1 + 4 - 2) = 5.
* • hold = max(-1, 5 - 4) = 1.
* 6. Day 5:
* • cash = max(5, 1 + 9 - 2) = 8 (Sell at price 9).
* • hold = max(1, 5 - 9) = 1.
*
* Output:
* • cash = 8 (Max profit).
*
*/
public int maxProfit_0_1(int[] prices, int fee) {
// Edge case
if (prices == null || prices.length == 0) {
return 0;
}
// Initialize states
int hold = -prices[0]; // Maximum profit when holding a stock
int cash = 0; // Maximum profit when not holding a stock
// Iterate through prices
for (int i = 1; i < prices.length; i++) {
/**
* NOTE !!! there are 2 dp equations (e.g. cash, hold)
*/
// Update cash and hold states
cash = Math.max(cash, hold + prices[i] - fee); // Sell the stock
hold = Math.max(hold, cash - prices[i]); // Buy the stock
}
// The maximum profit at the end is when not holding any stock
return cash;
}
2-3) N-th Tribonacci Number
// java
// LC 1137. N-th Tribonacci Number
// V0
// IDEA: DP (fixed by gpt)
public int tribonacci(int n) {
if (n == 0)
return 0;
if (n == 1 || n == 2)
return 1;
// NOTE !!! below, array size is `n + 1`
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
// NOTE !!! below, we loop from i = 3 to `i <= n`
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
}
return dp[n];
}