leetcode lecture on algorithm interview in Dachang 3. Dynamic programming
Video tutorial (efficient learning): Click to learn
catalog:
6. Depth first & breadth first
10. Recursion & divide and conquer
What is dynamic programming
Dynamic Programming, English: Dynamic Programming, referred to as DP, decomposes the problem into overlapping subproblems. Solving the original problem by repeatedly solving subproblems is Dynamic Programming. If a problem has many overlapping subproblems, it is more effective to use Dynamic Programming to solve it.
The core problem of solving dynamic programming is exhaustion, but this kind of problem is a little special because there are "overlapping subproblems" in this kind of problem. If it is brutally exhausted, the efficiency will be extremely low. The dynamic programming problem must have an "optimal substructure" in order to get the maximum value of the original problem through the maximum value of the subproblem. In addition, although the core idea of dynamic programming is to seek the maximum value by exhaustive method, the problem can be changeable. In fact, it is not easy to enumerate all feasible solutions. Only by listing * * the correct "state transition equation * * can we enumerate them correctly. Overlapping subproblem, optimal substructure and state transition equation are the three elements of dynamic programming
Differences between dynamic programming and other algorithms
 The difference between dynamic programming and divide and Conquer: both dynamic programming and divide and conquer have optimal substructures, but the sub problems of divide and conquer do not overlap
 The difference between dynamic programming and greedy: every state in dynamic programming must be derived from the previous state, which is different from greedy. Greedy has no state derivation, but directly selects the optimal solution from the local, so it is always the local optimal, but the global solution is not necessarily the optimal.
 The difference between dynamic programming and recursion: there may be a lot of repeated calculations between recursion and backtracking. Dynamic programming can reduce unnecessary repeated calculations by recursion and memory
Problem solving method of dynamic programming
 Recursion + memorization (topdown)
 Dynamic programming (bottomup)
Steps for solving dynamic programming problems
 Define status according to overlapping subproblems
 Finding the optimal substructure and deriving the state transition equation
 Determine dp initial state
 Determine output value
The solution of Fibonacci's dynamic programming
The animation is too large. Click to view it
Violent recursion
//Violent recursion complexity O(2^n) var fib = function (N) { if (N == 0) return 0; if (N == 1) return 1; return fib(N  1) + fib(N  2); };
Recursion + memorization
var fib = function (n) { const memo = {}; // Cache the calculated results const helper = (x) => { if (memo[x]) return memo[x]; if (x == 0) return 0; if (x == 1) return 1; memo[x] = fib(x  1) + fib(x  2); return memo[x]; }; return helper(n); };
dynamic programming
const fib = (n) => { if (n <= 1) return n; const dp = [0, 1]; for (let i = 2; i <= n; i++) { //Calculate each state from the bottom up dp[i] = dp[i  1] + dp[i  2]; } return dp[n]; };
Rolling array optimization
const fib = (n) => { if (n <= 1) return n; //The rolling array dp[i] is only related to dp[i1] and dp[i2]. It only maintains the rolling array with length 2 and constantly replaces the array elements const dp = [0, 1]; let sum = null; for (let i = 2; i <= n; i++) { sum = dp[0] + dp[1]; dp[0] = dp[1]; dp[1] = sum; } return sum; };
Dynamic programming + dimensionality reduction, (dimensionality reduction can reduce spatial complexity, but it is not conducive to program expansion)
var fib = function (N) { if (N <= 1) { return N; } let prev2 = 0; let prev1 = 1; let result = 0; for (let i = 2; i <= N; i++) { result = prev1 + prev2; //Just use two variables prev2 = prev1; prev1 = result; } return result; };
509. Fibonacci number(easy)
Method 1. Dynamic programming
 Idea: bottomup dynamic programming
 Complexity analysis: time complexity O(n), space complexity O(1)
Js:
var fib = function (N) { if (N <= 1) { return N; } let prev2 = 0; let prev1 = 1; let result = 0; for (let i = 2; i <= N; i++) { result = prev1 + prev2; prev2 = prev1; prev1 = result; } return result; };
Java:
class Solution { public int fib(int n) { if (n <= 1) { return n; } int prev2 = 0, prev1 = 1, result = 0; for (int i = 2; i <= n; i++) { result = prev2 + prev1; prev2 = prev1; prev1 = result; } return result; } }
62. Different paths (medium)
Method 1. Dynamic programming
The animation is too large. Click to view it
 Idea: since each position can only be down or right, the path sum of each coordinate is equal to the sum of different paths at the same position in the previous row and the same position in the previous column. The state transition equation: f[i][j] = f[i  1][j] + f[i][j  1];
 Complexity: time complexity O(mn). Spatial complexity O(mn), optimized O(n)
js:
var uniquePaths = function (m, n) { const f = new Array(m).fill(0).map(() => new Array(n).fill(0)); //Initial dp array for (let i = 0; i < m; i++) { //Initialize column f[i][0] = 1; } for (let j = 0; j < n; j++) { //Initialization line f[0][j] = 1; } for (let i = 1; i < m; i++) { for (let j = 1; j < n; j++) { f[i][j] = f[i  1][j] + f[i][j  1]; } } return f[m  1][n  1]; }; //State compression var uniquePaths = function (m, n) { let cur = new Array(n).fill(1); for (let i = 1; i < m; i++) { for (let r = 1; r < n; r++) { cur[r] = cur[r  1] + cur[r]; } } return cur[n  1]; };
Java:
class Solution { public int uniquePaths(int m, int n) { int[][] f = new int[m][n]; for (int i = 0; i < m; ++i) { f[i][0] = 1; } for (int j = 0; j < n; ++j) { f[0][j] = 1; } for (int i = 1; i < m; ++i) { for (int j = 1; j < n; ++j) { f[i][j] = f[i  1][j] + f[i][j  1]; } } return f[m  1][n  1]; } } //State compression class Solution { public int uniquePaths(int m, int n) { int[] cur = new int[n]; Arrays.fill(cur,1); for (int i = 1; i < m;i++){ for (int j = 1; j < n; j++){ cur[j] += cur[j1] ; } } return cur[n1]; } }
63. Different paths II(medium)
Method 1. Dynamic programming
 Idea: like question 62, the difference is to return to 0 directly when encountering obstacles
 Complexity: time complexity O(mn), space complexity O(mn), state compression followed by o(n)
Js:
var uniquePathsWithObstacles = function (obstacleGrid) { const m = obstacleGrid.length; const n = obstacleGrid[0].length; const dp = Array(m) .fill() .map((item) => Array(n).fill(0)); //Initial dp array for (let i = 0; i < m && obstacleGrid[i][0] === 0; ++i) { //Initial column dp[i][0] = 1; } for (let i = 0; i < n && obstacleGrid[0][i] === 0; ++i) { //Initial row dp[0][i] = 1; } for (let i = 1; i < m; ++i) { for (let j = 1; j < n; ++j) { //Return 0 directly when encountering obstacles dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i  1][j] + dp[i][j  1]; } } return dp[m  1][n  1]; }; //State compression var uniquePathsWithObstacles = function (obstacleGrid) { let m = obstacleGrid.length; let n = obstacleGrid[0].length; let dp = Array(n).fill(0); //Fill with 0 because there are obstacles. The value of the current dp array element is also related to obstacleGrid[i][j] dp[0] = 1; //The first column is temporarily filled with 1 for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (obstacleGrid[i][j] == 1) { //Pay attention to the conditions. When encountering obstacles dp[j] it becomes 0. The first column is included here dp[j] = 0; } else if (j > 0) { //Only when J > 0 is not the first column can j  1 be obtained dp[j] += dp[j  1]; } } } return dp[n  1]; };
Java:
class Solution { public int uniquePathsWithObstacles(int[][] obstacleGrid) { int n = obstacleGrid.length, m = obstacleGrid[0].length; int[] dp = new int[m]; dp[0] = obstacleGrid[0][0] == 0 ? 1 : 0; for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { if (obstacleGrid[i][j] == 1) { dp[j] = 0; continue; } if (j  1 >= 0 && obstacleGrid[i][j  1] == 0) { dp[j] += dp[j  1]; } } } return dp[m  1]; } }
70. Climb stairs (medium)
Method 1. Dynamic programming
 Idea: because you can climb 1 or 2 steps at a time, you can go up from n2 or n1 to the nth step, which is actually Fibonacci's dp equation
 Complexity analysis: time complexity O(n), space complexity O(1)
Js:
var climbStairs = function (n) { const memo = []; memo[1] = 1; memo[2] = 2; for (let i = 3; i <= n; i++) { memo[i] = memo[i  2] + memo[i  1];//Therefore, to the nth step, you can come up from n2 or n1 } return memo[n]; }; //State compression var climbStairs = (n) => { let prev = 1; let cur = 1; for (let i = 2; i < n + 1; i++) { [prev, cur] = [cur, prev + cur] // const temp = cur; // Staging the last cur // cur = prev + cur; // Current cur = last cur + last cur // prev = temp; // prev is updated to the last cur } return cur; }
Java:
class Solution { public int climbStairs(int n) { int prev = 1, cur = 1; for (int i = 2; i < n + 1; i++) { int temp = cur; cur = prev + cur; prev = temp; } return cur; } }
279. Complete square (medium)
Method 1: dynamic programming

Idea: dp[i] represents the minimum number of the complete square sum of I, dp[i  j * j] + 1 represents the number after subtracting the complete square of a complete square j and adding 1 equals dp[i]. As long as you find a smaller value in dp[i], dp[i  j * j] + 1, it is the value of the last dp[i].

Complexity: time complexity O(n* sqrt(n)), n is the input integer, which needs to be cycled n times. The complexity sqrt(n) and space complexity O(n) of dp equation are calculated each time
js:
var numSquares = function (n) { const dp = [...Array(n)].map((_) => 0); //Initialize dp array when n is 0 for (let i = 1; i <= n; i++) { dp[i] = i; // The worst case is to + 1 every time, for example: dp[3]=1+1+1 for (let j = 1; i  j * j >= 0; j++) {//Enumerate previous states dp[i] = Math.min(dp[i], dp[i  j * j] + 1); // Dynamic transfer equation } } return dp[n]; };
java:
class Solution { public int numSquares(int n) { int[] dp = new int[n]; for (int i = 1; i <= n; i++) { dp[i] = i; for (int j = 1; i  j * j >= 0; j++) { dp[i] = Math.min(dp[i], dp[i  j * j] + 1); } } return dp[n]; } }
120. Triangle minimum path and(medium)
Method 1. Dynamic programming
 Idea: traverse upward from the last layer of the triangle. The minimum path sum of each number is the smaller of the two numbers below it plus itself
 Complexity analysis: time complexity O(n^2), space complexity O(n)
Js:
const minimumTotal = (triangle) => { const h = triangle.length; // Initialize dp array const dp = new Array(h); for (let i = 0; i < h; i++) { dp[i] = new Array(triangle[i].length); } for (let i = h  1; i >= 0; i) { //Bottom up traversal for (let j = 0; j < triangle[i].length; j++) { //On the same floor if (i == h  1) { // base case bottom layer dp[i][j] = triangle[i][j]; } else { // The state transition equation is calculated from the upper layer to the lower layer dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]; } } } return dp[0][0]; }; //State compression const minimumTotal = (triangle) => { const bottom = triangle[triangle.length  1]; const dp = new Array(bottom.length); // base case is the last line for (let i = 0; i < dp.length; i++) { dp[i] = bottom[i]; } // Iterate from the penultimate column for (let i = dp.length  2; i >= 0; i) { for (let j = 0; j < triangle[i].length; j++) { dp[j] = Math.min(dp[j], dp[j + 1]) + triangle[i][j]; } } return dp[0]; };
Java:
class Solution { public int minimumTotal(List<List<Integer>> triangle) { int n = triangle.size(); int [] dp = new int [n]; for(int i = 0 ; i < n ; i++){ dp[i] = triangle.get(n1).get(i); } for(int i = n2 ; i >= 0 ; i){ for(int j = 0 ; j <= i ; j++){ dp[j] = triangle.get(i).get(j) + Math.min(dp[j] , dp[j+1]);//iteration } } return dp[0]; } }
152. Product maximum subarray (medium)
Method 1. Dynamic programming

Idea:

Status definition: dp[i][0] represents the minimum product of subarrays from item 0 to item I, and dp[i][1] represents the maximum product of subarrays from item 0 to item I

Initial state: dp[0][0]=nums[0], dp[0][1]=nums[0]

Discussion by case:
 Don't ride with others, just nums[i] yourself
 num[i] is a negative number. I want to multiply it by the previous maximum product
 num[i] is a positive number. I want to multiply it by the previous minimum product

State transition equation:
 dp[i] [0]=min(dp[i−1] [0]∗num[i] , dp[i−1] [1] ∗ num[i], num[i])
 dp[i] [1]=max(dp[i−1] [0]∗num[i] , dp[i−1] [1] ∗ num[i], num[i])

State compression: dp[i][x] is only associated with dp[i][x]1, so only two variables need to be defined, prevmin = num [0], prevmax = num [0]

Equation after state compression:
 prevMin = Math.min(prevMin * num[i], prevMax * num[i], nums[i])
 prevMax = Math.max(prevMin * num[i], prevMax * num[i], nums[i])


Complexity: time complexity O(n), space complexity O(1)
js:
var maxProduct = (nums) => { let res = nums[0] let prevMin = nums[0] let prevMax = nums[0] let temp1 = 0, temp2 = 0 for (let i = 1; i < nums.length; i++) { temp1 = prevMin * nums[i] temp2 = prevMax * nums[i] prevMin = Math.min(temp1, temp2, nums[i]) prevMax = Math.max(temp1, temp2, nums[i]) res = Math.max(prevMax, res) } return res }
Java:
class Solution { public int maxProduct(int[] nums) { int res = nums[0], prevMin = nums[0], prevMax = nums[0]; int temp1 = 0, temp2 = 0; for (int i = 1; i < nums.length; i++) { temp1 = prevMin * nums[i]; temp2 = prevMax * nums[i]; prevMin = Math.min(Math.min(temp1, temp2), nums[i]); prevMax = Math.max(Math.max(temp1, temp2), nums[i]); res = Math.max(prevMax, res); } return res; } }
Buying and selling stocks
121. The best time to buy and sell stocks (easy) limited number of transactions k=1
122. The best time to buy and sell stocks II (medium) unlimited number of transactions k = +infinity
123. The best time to buy and sell stocks III (hrad) limited number of transactions k=2
188. The best time to buy and sell stocks IV (hard) limit the number of transactions to k
309. The best time to buy and sell stocks includes the freezing period (medium) contains the transaction freeze period
714. The best time to buy and sell stocks includes handling fees (medium) each transaction includes handling fee
Questions 5 and 6 are equivalent to adding the conditions of freezing period and handling fee on the basis of question 2.
Restrictions
 Buy before you sell
 You cannot participate in multiple transactions at the same time. When you buy again, you need to sell first
 Only when k > = 0 can transactions be carried out, otherwise there are no transaction times
Define operation
 purchase
 sell out
 No operation
Define status
 i: Days
 k: The number of transactions. Each transaction includes buying and selling. Here, we only need to change k  1 when buying
 0: do not hold shares
 1: Holding shares
give an example
dp[i][k][0]//On day i, you can trade k times without stocks in your hands dp[i][k][1]//You can also trade stocks in your hand k times on day i
The ultimate maximum return is dp[n  1][k][0] instead of dp[n  1][k][1], because selling on the last day must be higher than holding
State transition equation
// There are two situations in which no stocks are held today // 1. dp[i  1][k][0], not held yesterday, not operated today. // 2. dp[i  1][k][1] + prices[i] held it yesterday, sold it today, and there are no stocks in hand today. dp[i][k][0] = Math.max(dp[i  1][k][0], dp[i  1][k][1] + prices[i]) // There are two situations for holding stocks today: // 1.dp[i  1][k][1] held yesterday, not operated today // 2.dp[i  1][k  1][0]  prices[i] did not hold yesterday, but bought today. dp[i][k][1] = Math.max(dp[i  1][k][1], dp[i  1][k  1][0]  prices[i]) //The maximum profit is the maximum of these two cases
121. The best time to buy and sell stocks (easy) limited number of transactions k=1
State transition equation
//Not holding on day i is transferred from the maximum values of not holding and then not operating on day i1 and holding and then selling on day i1 dp[i][1][0] = Math.max(dp[i  1][1][0], dp[i  1][1][1] + prices[i]) //The holding on day i is transferred from the maximum values of holding and then not operating on day i1 and not holding and then buying on day i1 dp[i][1][1] = Math.max(dp[i  1][1][1], dp[i  1][0][0]  prices[i]) = Math.max(dp[i  1][1][1], prices[i]) // When k=0, there are no transaction times, dp[i  1][0][0] = 0
k is a fixed value of 1, which does not affect the result, so it can be ignored. After simplification, it is as follows
dp[i][0] = Math.max(dp[i  1][0], dp[i  1][1] + prices[i]) dp[i][1] = Math.max(dp[i  1][1], prices[i])
Complete code
//Time complexity O(n) space complexity O(n), the second dimension of dp array is constant const maxProfit = function (prices) { let n = prices.length; let dp = Array.from(new Array(n), () => new Array(2)); dp[0][0] = 0; //Not held on day 0 dp[0][1] = prices[0]; //Held on day 0 for (let i = 1; i < n; i++) { dp[i][0] = Math.max(dp[i  1][0], dp[i  1][1] + prices[i]); dp[i][1] = Math.max(dp[i  1][1], prices[i]); } return dp[n  1][0]; };
State compression, dp[i] is only related to dp[i  1], remove one dimension
//Time complexity O(n) space complexity O(1) const maxProfit = function (prices) { let n = prices.length; let dp = Array.from(new Array(n), () => new Array(2)); dp[0] = 0; dp[1] = prices[0]; for (let i = 1; i < n; i++) { dp[0] = Math.max(dp[0], dp[1] + prices[i]); dp[1] = Math.max(dp[1], prices[i]); } return dp[0]; }; //Semantics const maxProfit = function (prices) { let n = prices.length; let sell = 0; let buy = prices[0]; for (let i = 1; i < n; i++) { sell = Math.max(sell, buy + prices[i]); buy = Math.max(buy, prices[i]); } return sell; };
122. The best time to buy and sell stocks II (medium) unlimited number of transactions k = +infinity
State transition equation
//Not holding on day i is transferred from the maximum values of not holding and then not operating on day i1 and holding and then selling on day i1 dp[i][k][0] = Math.max(dp[i  1][k][0], dp[i  1][k][1] + prices[i]) //The holding on day i is transferred from the maximum values of holding and then not operating on day i1 and not holding and then buying on day i1 dp[i][k][1] = Math.max(dp[i  1][k][1], dp[i  1][k  1][0]  prices[i])
k also does not affect the results. After simplification, it is as follows
dp[i][0] = Math.max(dp[i  1][0], dp[i  1][1] + prices[i]) dp[i][1] = Math.max(dp[i  1][1], dp[i  1][0]  prices[i])
Complete code
const maxProfit = function (prices) { let n = prices.length; let dp = Array.from(new Array(n), () => new Array(2)); dp[0][0] = 0; //Not held on day 0 dp[0][1] = prices[0]; //Price [0] spent buying on day 0 for (let i = 1; i < n; i++) { dp[i][0] = Math.max(dp[i  1][0], dp[i  1][1] + prices[i]); dp[i][1] = Math.max(dp[i  1][1], dp[i  1][0]  prices[i]); } return dp[n  1][0]; };
State compression, also dp[i] is only related to dp[i  1], remove one dimension
const maxProfit = function (prices) { let n = prices.length; let dp = Array.from(new Array(n), () => new Array(2)); dp[0] = 0; dp[1] = prices[0]; for (let i = 1; i < n; i++) { dp[0] = Math.max(dp[0], dp[1] + prices[i]); dp[1] = Math.max(dp[1], dp[0]  prices[i]); } return dp[0]; }; //Semantics const maxProfit = function (prices) { let n = prices.length; let sell = 0; let buy = prices[0]; for (let i = 1; i < n; i++) { sell = Math.max(sell, buy + prices[i]); buy = Math.max(buy, sell  prices[i]); } return sell; };
123. The best time to buy and sell stocks III (hrad) limited number of transactions k=2
State transition equation
dp[i][k][0] = Math.max(dp[i  1][k][0], dp[i  1][k][1] + prices[i]) dp[i][k][1] = Math.max(dp[i  1][k][1], dp[i  1][k  1][0]  prices[i])
k has an impact on the result and cannot be discarded. Only k can be cycled
for (let i = 0; i < n; i++) { for (let k = maxK; k >= 1; k) { dp[i][k][0] = Math.max(dp[i  1][k][0], dp[i  1][k][1] + prices[i]); dp[i][k][1] = Math.max(dp[i  1][k][1], dp[i  1][k  1][0]  prices[i]); } } //k=2, write the result of the loop directly dp[i][2][0] = Math.max(dp[i  1][2][0], dp[i  1][2][1] + prices[i]) dp[i][2][1] = Math.max(dp[i  1][2][1], dp[i  1][1][0]  prices[i]) dp[i][1][0] = Math.max(dp[i  1][1][0], dp[i  1][1][1] + prices[i]) dp[i][1][1] = Math.max(dp[i  1][1][1], dp[i  1][0][0]  prices[i]) = Math.max(dp[i  1][1][1], prices[i])// When k=0, there are no transaction times, dp[i  1][0][0] = 0 //Remove the dimension i dp[2][0] = Math.max(dp[2][0], dp[2][1] + prices[i]) dp[2][1] = Math.max(dp[2][1], dp[1][0]  prices[i]) dp[1][0] = Math.max(dp[1][0], dp[1][1] + prices[i]) dp[1][1] = Math.max(dp[1][1], dp[0][0]  prices[i]) = Math.max(dp[1][1], prices[i])// When k=0, there are no transaction times, dp[i  1][0][0] = 0
Complete code
//As before, we directly reduce the dimension const maxProfit = function (prices) { let buy_1 = prices[0], sell_1 = 0 let buy_2 = prices[0], sell_2 = 0 let n = prices.length for (let i = 1; i < n; i++) { sell_2 = Math.max(sell_2, buy_2 + prices[i]) buy_2 = Math.max(buy_2, sell_1  prices[i]) sell_1 = Math.max(sell_1, buy_1 + prices[i]) buy_1 = Math.max(buy_1, prices[i]) } return sell_2 }
188. The best time to buy and sell stocks IV (hard) limit the number of transactions to k
const maxProfit = function (k, prices) { let n = prices.length; let profit = new Array(k);//Like question 123, find the states of all k // Profit from buying and selling of initial k transactions for (let j = 0; j <= k; j++) { profit[j] = { buy: prices[0],//Indicates that there are shares sell: 0,//Indicates that there are no stocks }; } for (let i = 0; i < n; i++) { for (let j = 1; j <= k; j++) { //Question 122 can be traded countless times and 188 can be traded k times, so you can add a layer of k cycle directly //122 final recursive equation: //sell = Math.max(sell, buy + prices[i]); //buy = Math.max(buy, prices[i]); profit[j] = { sell: Math.max(profit[j].sell, profit[j].buy + prices[i]), buy: Math.max(profit[j].buy, profit[j  1].sell  prices[i]), }; } } return profit[k].sell; //Returns the maximum profit after clearing the stock for the kth time };
309. The best time to buy and sell stocks includes the freezing period (medium) contains the transaction freeze period
State transition equation
dp[i][k][0] = Math.max(dp[i  1][k][0], dp[i  1][k][1] + prices[i]) //The cooling time is 1 day, so it is necessary to transfer from i  2 days //Buy, sell  frozen period  buy, sell dp[i][k][1] = Math.max(dp[i  1][k][1], dp[i  2][k  1][0]  prices[i])
The title does not limit the size of k and can be omitted
dp[i][0] = Math.max(dp[i  1][0], dp[i  1][1] + prices[i]) dp[i][1] = Math.max(dp[i  1][1], dp[i  2][0]  prices[i]) //Dimension reduction i dp[0] = Math.max(dp[0], dp[1] + prices[i]) dp[1] = Math.max(dp[1], profit_freeze  prices[i])
Complete code
const maxProfit = function (prices) { let n = prices.length; let buy = prices[0];//Stock in hand let sell = 0;//No stock let profit_freeze = 0; for (let i = 1; i < n; i++) { let temp = sell; sell = Math.max(sell, buy + prices[i]); buy = Math.max(buy, profit_freeze  prices[i]); profit_freeze = temp; } return sell; };
714. The best time to buy and sell stocks includes handling fees (medium) each transaction includes handling fee
State transition equation
//There is a handling charge for each transaction. We define that the handling charge is deducted when selling dp[i][0] = max(dp[i  1][0], dp[i  1][1] + prices[i]  fee) dp[i][1] = max(dp[i  1][1], dp[i  1][0]  prices[i])
Complete code
const maxProfit = function (prices, fee) { let sell = 0;//sell out let buy = prices[0];//purchase for (let i = 1; i < prices.length; i++) { sell = Math.max(sell, buy + prices[i]  fee); buy = Math.max(buy, sell  prices[i]); } return sell; };
322. Change (medium)
You can't use greed. For the counter example, coins=[1, 3, 5, 6, 7], amount=30. Use greed to use the largest denomination 7 first, and use two 1, 4 * 7 + 2 * 1 = 30. However, we can use five 6, 5 * 6 = 30 to exchange with the least coins
Method 1. Dynamic programming
 Idea: DP [i] represents the minimum coins needed to exchange denomination I. because the coins are infinite, DP [i] can be calculated from bottom to top. For each state of dp[0~i], cycle the coins array to find a combination that can be exchanged, subtract the current coin value from the denomination I, and dp[icoin] is DP [i] after adding a number of coins. Finally, the minimum value is the answer, and the state transition equation is DP [i] = Math.min(dp[i], dp[icoin] + 1);
 Complexity analysis: the time complexity is O(sn), s is the exchange amount, and N is the length of the coin array. A total of s states need to be calculated, and each state needs to traverse n denominations to transfer the state. The space complexity is O(s), that is, the length of the dp array
Js:
var coinChange = function (coins, amount) { let dp = new Array(amount + 1).fill(Infinity);//Initialize dp array dp[0] = 0;//Only 0 coins are needed for denomination 0 for (let i = 1; i <= amount; i++) {//Circulating denomination for (let coin of coins) {//Circular coin array if (i  coin >= 0) {//When the denomination is greater than the value of the coin //dp[i  coin]: the minimum coin required for the current denomination i minus the current coin value //dp[i] can be converted from dp[i  coin] + 1 dp[i] = Math.min(dp[i], dp[i  coin] + 1); } } } return dp[amount] === Infinity ? 1 : dp[amount];//If DP [amount] = = infinity, it cannot be redeemed };
Java:
public class Solution { public int coinChange(int[] coins, int amount) { int max = amount + 1; int[] dp = new int[amount + 1]; Arrays.fill(dp, max); dp[0] = 0; for (int i = 1; i <= amount; i++) { for (int j = 0; j < coins.length; j++) { if (coins[j] <= i) { dp[i] = Math.min(dp[i], dp[i  coins[j]] + 1); } } } return dp[amount] > amount ? 1 : dp[amount]; } }
72. Edit distance (hard)
Method 1. Dynamic programming
 Idea: dp[i][j] represents the minimum editing distance between the first I characters of word1 and the first j characters of word2.
 If word1 [I1] = = word2 [J1], it means that the last character does not need to be operated. At this time, dp[i][j] = dp[i1][j1], that is, the minimum operand at this time is the same as the minimum editing number of one character reduced by word1 and word2
 If word1 [I1]== Word2 [J1], there are three cases
 word1 deletes the last character, and the status changes to dp[i1][j], that is, dp[i][j] = dp[i1][j] + 1, + 1 refers to the deletion operation
 word1 adds a character at the end, and the status changes to dp[i][j1], that is, dp[i][j] = dp[i][j1] + 1, + 1 refers to the increase operation
 word1 replaces the last character, and the status changes to dp[i1][j1], that is, DP [i] [J] = dp[i1][j1] + 1, + 1 refers to the replacement operation
 Complexity: the time complexity is O(mn), m is the length of word1, and N is the length of word2. The spatial complexity is O(mn), and a twodimensional digital storage state with the size of m * n is required.
Js:
const minDistance = (word1, word2) => { let dp = Array.from(Array(word1.length + 1), () => Array(word2.length + 1).fill(0)); //Initializing the array requires at least i operations for the first i characters of word1. For example, i deletes and becomes word2 for (let i = 1; i <= word1.length; i++) { dp[i][0] = i; } //Initializing the array requires at least i operations for the first i characters of word2, such as j inserts into word1 for (let j = 1; j <= word2.length; j++) { dp[0][j] = j; } for (let i = 1; i <= word1.length; i++) { //Loop word1 and word2 for (let j = 1; j <= word2.length; j++) { if (word1[i  1] === word2[j  1]) { //If word1 [I1] = = word2 [J1], the last character does not need to be operated. dp[i][j] = dp[i  1][j  1]; } else { //dp[i1][j] + 1: corresponding deletion //dp[i][j1] + 1: add corresponding // dp[i1][j1] + 1: corresponding replacement operation dp[i][j] = Math.min(dp[i  1][j] + 1, dp[i][j  1] + 1, dp[i  1][j  1] + 1); } } } return dp[word1.length][word2.length]; };
Java:
public int minDistance(String word1, String word2) { int m = word1.length(); int n = word2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { dp[i][0] = i; } for (int j = 1; j <= n; j++) { dp[0][j] = j; } for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (word1.charAt(i  1) == word2.charAt(j  1)) { dp[i][j] = dp[i  1][j  1]; } else { dp[i][j] = Math.min(Math.min(dp[i  1][j  1], dp[i][j  1]), dp[i  1][j]) + 1; } } } return dp[m][n]; }
10. Regular expression matching(hard)
Method 1. Dynamic programming
 Idea: dp[i][j] indicates whether the first I characters of s can match the first j characters of p. there are four cases. See the figure
 Complexity: time complexity O(mn), m and N are the lengths of strings S and p respectively, and nested loops s and p are required. Space complexity O(mn), space occupied by dp array
js:
//dp[i][j] indicates whether the first I characters of s can match the first j characters of p const isMatch = (s, p) => { if (s == null  p == null) return false;//In extreme cases, both s and p are null and return false const sLen = s.length, pLen = p.length; const dp = new Array(sLen + 1);//Because the position starts from 0 and the 0th position is an empty string, the initialization length is sLen + 1 for (let i = 0; i < dp.length; i++) {//Initialize dp array dp[i] = new Array(pLen + 1).fill(false); // Default item to false } // base case s and p match at position 0 dp[0][0] = true; for (let j = 1; j < pLen + 1; j++) {//Initialize the first column of dp. At this time, the position of s is 0 //Case 1: if the j1 position of p is *, the state of j is equal to the state of j2 //For example: S = 'p ='a *' is equivalent to looking forward 2 positions of P. if it matches, * is equivalent to repeating 0 characters if (p[j  1] == "*") dp[0][j] = dp[0][j  2]; } // iteration for (let i = 1; i < sLen + 1; i++) { for (let j = 1; j < pLen + 1; j++) { //Case 2: if the current characters of s and p are equal or the current position of p is. Then the current dp[i][j] can be transferred from dp[i  1][j  1] //If the current position matches, both s and p look forward one bit. If all the previous characters match, all the characters in front of the current position also match //For example: S ='xxxa 'p ='xxx.' or S ='xxxa 'p ='xxxa' if (s[i  1] == p[j  1]  p[j  1] == ".") { dp[i][j] = dp[i  1][j  1]; } else if (p[j  1] == "*") {//Case 3: enter the branch where the current character does not match. If the current p is *, it may match //If the current position of s is the same as the previous position of p or the previous position of p is equal to. There are three possibilities //If one of the conditions can be matched, the status of the current position can also be matched //dp[i][j  2]: p look forward for 2 positions, which is equivalent to * repeating 0 times, //dp[i][j  1]: p looks forward 1 position, which is equivalent to * repeating 1 time //dp[i  1][j]: s looks forward one position, which is equivalent to * repeating n times //For example, s ='xxxa 'p ='xxxa *' if (s[i  1] == p[j  2]  p[j  2] == ".") { dp[i][j] = dp[i][j  2]  dp[i][j  1]  dp[i  1][j]; } else { //Case 4: if the current position of s does not match the first two positions of p, it is equivalent to * repeated 0 times //For example, the status of S ='xxxb 'p ='xxxa *' current position is the same as that of P looking forward dp[i][j] = dp[i][j  2]; } } } } return dp[sLen][pLen]; // Does the s string with length sLen match the p string with length pnen };
Java:
class Solution { public boolean isMatch(String s, String p) { if (p==null){ if (s==null){ return true; }else{ return false; } } if (s==null && p.length()==1){ return false; } int m = s.length()+1; int n = p.length()+1; boolean[][]dp = new boolean[m][n]; dp[0][0] = true; for (int j=2;j<n;j++){ if (p.charAt(j1)=='*'){ dp[0][j] = dp[0][j2]; } } for (int r=1;r<m;r++){ int i = r1; for (int c=1;c<n;c++){ int j = c1; if (s.charAt(i)==p.charAt(j)  p.charAt(j)=='.'){ dp[r][c] = dp[r1][c1]; }else if (p.charAt(j)=='*'){ if (p.charAt(j1)==s.charAt(i)  p.charAt(j1)=='.'){ dp[r][c] = dp[r1][c]  dp[r][c2]; }else{ dp[r][c] = dp[r][c2]; } }else{ dp[r][c] = false; } } } return dp[m1][n1]; } }
312. Poke the balloon (hard)
Method 1: dynamic programming
 Idea: dp[i][j] represents the gold coins that can be obtained by opening the interval (i,j). K is the last balloon to be punctured in this interval. Enumerate I and j and traverse all intervals. The maximum number of gold coins that can be obtained by ij is equal to the money obtained by puncturing the current balloon plus the gold coins already obtained in the previous ik and kj zones
 Complexity: time complexity O(n^3), n is the number of balloons, threelevel traversal. Space complexity O(n^2), dp array space.
js:
var maxCoins = function (nums) { const n = nums.length; let points = [1, ...nums, 1]; //Add virtual balloons on both sides const dp = Array.from(Array(n + 2), () => Array(n + 2).fill(0)); //dp array initialization //Bottom up transition state for (let i = n; i >= 0; i) { //i decreasing for (let j = i + 1; j < n + 2; j++) { //j expanding for (let k = i + 1; k < j; k++) { //Enumerate all possible of k in i and j //The maximum amount of gold coins ij can get is equal to the money obtained by puncturing the current balloon plus the gold coins obtained in the previous ik and KJ intervals dp[i][j] = Math.max( //Challenge maximum dp[i][j], dp[i][k] + dp[k][j] + points[j] * points[k] * points[i] ); } } } return dp[0][n + 1]; };
java:
class Solution { public int maxCoins(int[] nums) { int n = nums.length; int[][] dp = new int[n + 2][n + 2]; int[] val = new int[n + 2]; val[0] = val[n + 1] = 1; for (int i = 1; i <= n; i++) { val[i] = nums[i  1]; } for (int i = n  1; i >= 0; i) { for (int j = i + 2; j <= n + 1; j++) { for (int k = i + 1; k < j; k++) { int sum = val[i] * val[k] * val[j]; sum += dp[i][k] + dp[k][j]; dp[i][j] = Math.max(dp[i][j], sum); } } } return dp[0][n + 1]; } }
343. Integer splitting (medium)
 Idea: dp[i] is the maximum product of the positive integer I after splitting, and the cyclic number n. split each number and take the maximum product. The state transition equation: dp[i] = Math.max(dp[i], dp[i  j] * j, (i  j) * j), j*(ij) means to split I into J and ij, and j * dp[ij] means to split I into J and continue to split (ij) and take (ij) The maximum product in the split result is multiplied by J
 Complexity: time complexity O(n^2), twolevel cycle. Space complexity O(n), dp array space
js:
var integerBreak = function (n) { //dp[i] is the maximum product of positive integer I after splitting let dp = new Array(n + 1).fill(0); dp[2] = 1; for (let i = 3; i <= n; i++) { for (let j = 1; j < i; j++) { //j*(ij) means dividing i into j and ij and multiplying them //j*dp[ij] means splitting i into j and continuing to split the number (ij), taking the maximum product of (ij) splitting result and multiplying it by j dp[i] = Math.max(dp[i], dp[i  j] * j, (i  j) * j); } } return dp[n]; };
java:
class Solution { public int integerBreak(int n) { int[] dp = new int[n+1]; dp[2] = 1;//Initial state for (int i = 3; i <= n; ++i) { for (int j = 1; j < i  1; ++j) { dp[i] = Math.max(dp[i], Math.max(j * (i  j), j * dp[i  j])); } } return dp[n]; } }
01 knapsack problem
01 knapsack problem refers to the knapsack with n items and capacity of j. the weight array records the weight of n items, the weight of the item at position I is weight[i], the value array records the value of n items, and the value of the item at position I is values [i]. Each item can only be put into the knapsack once. Ask to load those items into the knapsack to maximize the value of the knapsack.
give an example:
We do it by dynamic programming

Status definition: dp[i][j] refers to the maximum value of taking any item from the previous I and putting it into the backpack with capacity j

State transition equation: dp[i][j] = max(dp[i  1][j], dp[i  1][j  weight[i]] + value[i]); Each item can be put into the backpack or not
 When j  weight [i] < 0: it means that no I element can be loaded and will not be put into the backpack. At this time, dp[i][j] = dp[i  1][j], dp[i] [j] depends on the maximum value of the items in the first i1 loaded into the backpack with capacity j
 When J  weight [i] > = 0: you can choose to put it in or not put it in the backpack.
Put it into the backpack: dp[i][j] = DP [I  1] [jweight[i]] + value[i], DP [I  1] [jweight[i]] represents the maximum value of the items in i1 loaded into the backpack with the capacity of jweight[i], and then add the value[i] of the items to transfer the state to dp[i][j].
If it is not put into the backpack, dp[i][j] = dp[i  1] [j], whichever is greater.

Initialize dp array: dp[i][0] indicates that the volume of the backpack is 0, then the value of the backpack must be 0, and dp[0][j] indicates the value of the backpack after item 0 is put into the backpack

Finally, you need to return the value: the last column of the last row of the dp array
The dp array after the loop is completed is shown in the following figure
js:
function testWeightBagProblem(wight, value, size) { const len = wight.length, dp = Array.from({ length: len + 1 }).map(//Initialize dp array () => Array(size + 1).fill(0) ); //Note that we let i start with 1, because we sometimes use i  1 to prevent the array from going out of bounds //Therefore, when initializing the dp array, the length is wight.length+1 for (let i = 1; i <= len; i++) { for (let j = 0; j <= size; j++) { //Because the length of weight is weight. Length + 1, and the subscript of the item starts from 1, i should be reduced by 1 if (wight[i  1] <= j) { dp[i][j] = Math.max( dp[i  1][j], value[i  1] + dp[i  1][j  wight[i  1]] ) } else { dp[i][j] = dp[i  1][j]; } } } return dp[len][size]; } function test() { console.log(testWeightBagProblem([1, 3, 4], [15, 20, 30], 4)); } test();
State compression
According to the state transition equation dp[i][j] = max(dp[i  1][j], dp[i  1][j  weight[i]] + value[i]), line I is only related to the state of line i1, so we can use the rolling array for state compression. Secondly, we note that j is only related to the state in front of J, so we can use only one array to calculate the state from back to front.
The animation is too large. Click to view it
function testWeightBagProblem2(wight, value, size) { const len = wight.length, dp = Array(size + 1).fill(0); for (let i = 1; i <= len; i++) { //It is calculated from back to front. If it is from front to back, the latest value will overwrite the old value, resulting in incorrect calculation results //dp[i][j] = Math.max(dp[i  1][j], dp[i  1][j  wight[i  1]] + value[i  1]) for (let j = size; j >= wight[i  1]; j) { dp[j] = Math.max(dp[j], dp[j  wight[i  1]] + value[i  1] ); } } return dp[size]; }
416. Segmentation and subsets (medium)
 Idea: this problem can be regarded as a 01 knapsack problem. Give a knapsack with a loadable weight of sum / 2 and N items. The weight of each item is recorded in the num array. Ask whether the knapsack can be filled exactly in one loading method? dp[i][j] indicates whether the first I items can fill the backpack with volume j. when dp[i][j] is true, it indicates that it can be filled exactly. Each number can be put into the backpack or not. The analysis method is the same as that of the 01 backpack problem.
 Complexity: time complexity O(n*sum), n is the length of num array, and sum is the sum of num array elements. The space complexity is O(n*sum), and the state compression is followed by O(sum)
js:
//It can be regarded as a 01 knapsack problem. Give a knapsack with a loadable weight of sum / 2 and N items, //The weight of each item is recorded in the num array. Is it possible to fill the backpack exactly in one loading method? var canPartition = function (nums) { let sum = 0 let n = nums.length for (let i = 0; i < n; i++) { sum += nums[i] } if (sum % 2 !== 0) {//If it is an odd number, it cannot be divided and returns false directly return false } sum = sum / 2 //dp[i][j] indicates whether the first I items can fill the backpack with volume j. when dp[i][j] is true, it indicates that it can be filled exactly //Finally, dp[n][sum] indicates whether the first n items can just fill the backpack with sum capacity //The length of dp array is n+1, and it is a twodimensional array. The first dimension represents the index of items, and the second dimension represents the backpack size let dp = new Array(n + 1).fill(0).map(() => new Array(sum + 1).fill(false)) //dp array initialization, dp[..][0] = true indicates that the backpack capacity is 0, and it is full at this time, //dp[0][..] = false indicates that there are no items. It must be filled with insufficient items for (let i = 0; i <= n; i++) { dp[i][0] = true } for (let i = 1; i <= n; i++) {//i starts traversing from 1 to prevent the array from crossing the boundary when taking dp[i  1][j] let num = nums[i  1] //J starts from 1 and j is 0, which has been completed during the initialization of dp array for (let j = 1; j <= sum; j++) { if (j  num < 0) {//The capacity of the backpack is insufficient and cannot be put into the backpack dp[i][j] = dp[i  1][j];//dp[i][j] depends on whether the first i1 items can fill the capacity of j } else { //dp[i  1][j] indicates that the ith item is not loaded //dp[i  1][jnum] indicates loading the ith. At this time, you need to look forward to see if the front i  1 can be filled with jnum //The difference between backpack and backpack is that it only returns true and false to indicate whether it can be filled, without calculating the value dp[i][j] = dp[i  1][j]  dp[i  1][j  num]; } } } return dp[n][sum] }; //State transition equation f [I, target] = f [I  1, target]  f [I  1, target  nums [i]] //The state of line n depends only on the state of line n1 //State compression var canPartition = function (nums) { let sum = nums.reduce((acc, num) => acc + num, 0); if (sum % 2) { return false; } sum = sum / 2; const dp = Array.from({ length: sum + 1 }).fill(false); dp[0] = true; for (let i = 1; i <= nums.length; i++) { //It is calculated from back to front. If it is from front to back, the latest value will overwrite the old value, resulting in incorrect calculation results for (let j = sum; j > 0; j) { dp[j] = dp[j]  (j  nums[i] >= 0 && dp[j  nums[i]]); } } return dp[sum]; };
java:
public class Solution { public boolean canPartition(int[] nums) { int len = nums.length; int sum = 0; for (int num : nums) { sum += num; } if ((sum & 1) == 1) { return false; } int target = sum / 2; boolean[][] dp = new boolean[len][target + 1]; dp[0][0] = true; if (nums[0] <= target) { dp[0][nums[0]] = true; } for (int i = 1; i < len; i++) { for (int j = 0; j <= target; j++) { dp[i][j] = dp[i  1][j]; if (nums[i] <= j) { dp[i][j] = dp[i  1][j]  dp[i  1][j  nums[i]]; } } if (dp[i][target]) { return true; } } return dp[len  1][target]; } } //State compression public class Solution { public boolean canPartition(int[] nums) { int len = nums.length; int sum = 0; for (int num : nums) { sum += num; } if ((sum & 1) == 1) { return false; } int target = sum / 2; boolean[] dp = new boolean[target + 1]; dp[0] = true; if (nums[0] <= target) { dp[nums[0]] = true; } for (int i = 1; i < len; i++) { for (int j = target; nums[i] <= j; j) { if (dp[target]) { return true; } dp[j] = dp[j]  dp[j  nums[i]]; } } return dp[target]; } }
198. House raiding (medium)
 Idea: dp[i] represents the maximum amount that 0i can steal, and dp[i] is transferred from the maximum value in the two cases
 dp[i2] + nums [i] means stealing the current location, so the location of i1 cannot be stolen, and dp[i2] needs to be added, that is, the money of the first i2 rooms
 dp[i  1] means stealing the current location, only i1 rooms
 Complexity: time complexity O(n), traversing the array once, space complexity O(1), O(1) after state compression, O(n) without state compression
js:
//dp[i] represents the maximum amount that 0i can steal const rob = (nums) => { const len = nums.length; const dp = [nums[0], Math.max(nums[0], nums[1])]; //Initializes the first two items of the dp array for (let i = 2; i < len; i++) { //Traverse from the third position //dp[i  2] + nums[i] means stealing the current location, so the location of i1 cannot be stolen, //And you need to add dp[i2], that is, the money for the first i2 rooms //dp[i  1] means stealing the current location, only i1 rooms dp[i] = Math.max(dp[i  2] + nums[i], dp[i  1]); } return dp[len  1]; //Returns the last largest item }; //State compression var rob = function (nums) { if(nums.length === 1) return nums[0] let len = nums.length; let dp_0 = nums[0], dp_1 = Math.max(nums[0], nums[1]); let dp_max = dp_1; for (let i = 2; i < len; i++) { dp_max = Math.max( dp_1, //Don't rob the current home dp_0 + nums[i] //Rob the current home ); dp_0 = dp_1; //Rolling exchange variable dp_1 = dp_max; } return dp_max; };
java:
class Solution { public int rob(int[] nums) { if (nums == null  nums.length == 0) { return 0; } int length = nums.length; if (length == 1) { return nums[0]; } int[] dp = new int[length]; dp[0] = nums[0]; dp[1] = Math.max(nums[0], nums[1]); for (int i = 2; i < length; i++) { dp[i] = Math.max(dp[i  2] + nums[i], dp[i  1]); } return dp[length  1]; } } //State compression class Solution { public int rob(int[] nums) { if (nums == null  nums.length == 0) { return 0; } int len = nums.length; int dp_0 = 0, dp_1 = nums[0]; int dp_max = nums[0]; for (int i = 2; i <= len; i++) { dp_max = Math.max( dp_1, //Don't rob the current home dp_0 + nums[i  1] //Rob the current home ); dp_0 = dp_1; //Rolling exchange variable dp_1 = dp_max; } return dp_max; } }
64. Minimum path and (medium)
 Idea: dp[i][j] represents the minimum path sum from the upper left corner of the matrix to the grid (I, j). As long as the grid is traversed from top to bottom and from left to right, the current minimum path sum is the current value plus the upper and left small ones.
 Complexity: time complexity O(mn), m and n are the length and width of the matrix respectively. If the space complexity is modified in place, it is O(1), and if the new dp array is created, it is O(mn)
js:
var minPathSum = function(dp) { let row = dp.length, col = dp[0].length for(let i = 1; i < row; i++)//Initialize the first column dp[i][0] += dp[i  1][0] for(let j = 1; j < col; j++)//Initialize the first line dp[0][j] += dp[0][j  1] for(let i = 1; i < row; i++) for(let j = 1; j < col; j++) dp[i][j] += Math.min(dp[i  1][j], dp[i][j  1])//Take the smallest one on the top and left return dp[row  1][col  1] };
java:
class Solution { public int minPathSum(int[][] grid) { if (grid == null  grid.length == 0  grid[0].length == 0) { return 0; } int rows = grid.length, columns = grid[0].length; int[][] dp = new int[rows][columns]; dp[0][0] = grid[0][0]; for (int i = 1; i < rows; i++) { dp[i][0] = dp[i  1][0] + grid[i][0]; } for (int j = 1; j < columns; j++) { dp[0][j] = dp[0][j  1] + grid[0][j]; } for (int i = 1; i < rows; i++) { for (int j = 1; j < columns; j++) { dp[i][j] = Math.min(dp[i  1][j], dp[i][j  1]) + grid[i][j]; } } return dp[rows  1][columns  1]; } }