preface
Dynamic programming (DP) is a very important knowledge point in computer programming algorithms. Whether it is school recruitment or social recruitment, interviewers often like to ask such programming questions to investigate the programming ability of interviewers. This blog mainly summarizes the main idea of dp, and then focuses on the explanation of stock trading in leetcode.
DP Basics
DP is simple and can be summarized as "one model with three characteristics".
- "One model" refers to the model suitable for solving the problem of dynamic programming, that is, the optimal solution model of multi-stage decision-making (this model is also a model of backtracking greedy problem-solving). Generally, when dynamic programming is used to solve the optimal problem, it needs to go through multiple decision-making stages. Each decision stage corresponds to a set of states. Then we look for a set of decision sequences. Through this set of decision sequences, we can produce the optimal value expected to be solved at the end.
- "Three characteristics" refer to: optimal substructure, no aftereffect and repeated subproblem.
Remember that the most important core of the dp problem is to write the state transition equation
The idea of state transition equation method: find the optimal substructure - write the state transition equation - translate the state transition equation into code
After summarizing the above knowledge, readers can leave an impression. Let's take a look at the specific exercises.
Classic exercises
The shortest path problem of Yang Hui triangle
How long is the shortest path from the first layer to the last layer
As shown in the left figure above, that is, our common Yang Hui triangle structure. In the storage representation of the computer, its form is as shown in the right figure above. The motion direction of each node is shown in the figure. According to the motion direction of the node value, we can clearly summarize the dp state transition equation dp[i][j] of the problem.
The code is as follows:
public int shortestPath(int[][] array) { //First, define a two-dimensional state transition array. The middle value stores the current path int[][] dp = new int[array.length][array[array.length - 1].length]; dp[0][0] = array[0][0]; //The process of DP is as follows: drawing can be obtained // The state value corresponding to the first column of elements will only be states[i][0]=states[i-1][0]+tri[i][0] obtained from the downward movement direction //The states[i][j] in the middle can be obtained from the smaller values of [i-1][j-1] and [i-1][j] // The last element [i][j] of each line is obtained by [i-1][j-1] for (int i = 1; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { if (j == 0) dp[i][j] = dp[i - 1][j] + array[i][j]; else if (j == array[i].length - 1) dp[i][j] = dp[i - 1][j - 1] + array[i][j]; else dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]) + array[i][j]; } } //The output minimum value is the minimum value in the last row of the two-dimensional array int min = Integer.MAX_VALUE; for (int j = 0; j < dp[array.length - 1].length; j++) { min = Math.min(min, dp[array.length - 1][j]); } return min; }
Shortest path from top left to bottom right
In a two-dimensional array, the motion direction of each node can only be down or right. Solve the shortest path from top left to bottom right:
Because it is similar to the shortest path of Yang Hui triangle in the previous question, I won't repeat it here. dp state transition equation is shown in the figure.
public int shortestPath(int[][] a) { int n = a.length, m = a[0].length; int[][] states = new int[n][m]; Define state transition array //Start writing DP process //The value states[i][j] for most locations is obtained from min(states[i-1][j],states[i][j-1]) //The first row can only move to the right and the first column can only move down states[0][0] = a[0][0]; for (int i = 1; i < m; i++) { states[0][i] = states[0][i - 1] + a[0][i]; //first line } for (int i = 1; i < n; i++) { for (int j = 0; j < m; j++) { if (j == 0) states[i][j] = states[i - 1][j] + a[i][j]; //First column else { states[i][j] = Math.min(states[i - 1][j], states[i][j - 1]) + a[i][j]; } } } //Output results return states[n - 1][m - 1]; }
The code time complexity is O(nm) and the space complexity is O(nm)
By observing the state transition equation, you can also simplify the code recursively. Recurrence formula: min_dist(i,j)=a[i][j]+min(min_dist(i-1,j),min_dist(i,j-1))
public int shortestPath2(int[][] a) { int n = a.length, m = a[0].length; int[][] states = new int[n][m]; Define state transition array return recursion(n - 1, m - 1, states, a); } public int recursion(int i, int j, int[][] states, int[][] a) { //Call from states[n-1][m-1] if (i == 0 && j == 0) return a[i][j]; //Recursive termination condition if (states[i][j] > 0) return states[i][j]; int minLeft = Integer.MAX_VALUE; if (j - 1 >= 0) { //Non first column states[i][j] can be obtained from states[i][j-1] minLeft = recursion(i, j - 1, states, a); } int minUp = Integer.MAX_VALUE; if (i - 1 >= 0) { //Non first row states[i][j] can be obtained from states[i-1][j] minUp = recursion(i - 1, j, states, a); } states[i][j] = a[i][j] + Math.min(minLeft, minUp); return states[i][j]; }
How many middle steps are there from top left to bottom right
In a two-dimensional array, the element value of 1 indicates that the stone is walkable, and the element value of 0 indicates that the stone is not passable. How many paths can you reach from the top left to the bottom right.
public int countPath(int[][] a) { int n = a.length, m = a[0].length; //First, define a state transition table int[][] state = new int[n][m]; //The first line can only go to the right. The method is 1 for (int i = 0; i < m; i++) { state[0][i] = a[0][i]; } for (int i = 1; i < n; i++) { for (int j = 0; j < m; j++) { //The first column is 1, which can only be obtained downward if (j == 0) state[i][j] = a[i][j]; else { //Here we need to judge whether it is a stone or not if (a[i][j] == 0) state[i][j] = 0; else state[i][j] = state[i - 1][j] + state[i][j - 1]; //Recurrence formula } } } return state[n - 1][m - 1]; }
0-1 knapsack problem
0-1 knapsack problem 1 considers the weight of items and the maximum weight borne by the knapsack
The maximum weight that the backpack can bear is w. there is a set of data weight representing the weight of each item. What is the maximum weight of the backpack?
For example, w=9,weight=[2, 2, 4, 6, 3]. We divide the whole solution process into n stages. Each stage determines whether a commodity is loaded into the backpack. After each item is decided, the maximum weight of the backpack will change, that is, there are different nodes in the recursive tree corresponding to different states. Look closely, have you found that this is a multi-stage decision-making optimal solution model. 01 knapsack problem is indeed dp the most classic problem.
The conversion code is as follows:
public int DPSlove(int[] weight, int w) { int n = weight.length; boolean[][] state = new boolean[n][w + 1]; //Define state transition table (2D array) state[0][0] = state[0][weight[0]] = true; //State transition using dynamic programming for (int i = 1; i < n; i++) { for (int j = 0; j <= w; j++) { //Don't put the ith item into the backpack because the weight state of this subscript already exists if (state[i - 1][j]) state[i][j] = true; } for (int j = 0; j <= w - weight[i]; j++) { //Put the i-th item into the backpack if (state[i - 1][j]) state[i][j + weight[i]] = true; } } //Output results for (int i = w; i >= 0; i--) { if (state[n - 1][i] == true) return i; } return 0; }
The time complexity is O(nw) and the space complexity is O(nw)
Observe that the judgment of not putting the ith commodity into the backpack in the above code for loop is unnecessary, and the final output result will only be related to the last row array of dp array. Therefore, we can optimize the following code:
- dp array is changed to one bit array to reduce space consumption
- The 0 1 knapsack problem can be simplified to rely only on v whether the current item is put into the knapsack
/** * In this function, we do spatial optimization for the above DP process * Here, we only need to use a one-dimensional array with the size of w+1 */ public int DPSlove2(int[] weight, int w) { int n = weight.length; boolean[] states = new boolean[w + 1]; states[0] = states[weight[0]] = true; for (int i = 1; i < n; i++) { //DP process for (int j = w - weight[i]; j >= 0; j--) { //Here, j needs to be processed from large to small. If j is from small to large, there will be the problem of repeated calculation of the for loop //Put the i-th item into the backpack if (states[j]) states[j + weight[i]] = true; } } for (int i = w; i >= 0; i--) { //Output results if (states[i]) return i; } return 0; }
0-1 knapsack problem 2 consider the maximum weight of the knapsack, the weight and value of each item
Compared with the previous exercise,
int w = 9, n = 5;
int[] weight = {2, 2, 4, 6, 3};
int[] value = {3, 4, 8, 9, 6};
The previous state [] [] stores boolean data, indicating whether the element is put or not. Here, it can be changed to the total value of the items contained in the current backpack.
public int[] DPSlove(int[] weight, int[] value, int n, int w) { int[][] state = new int[n][w + 1]; //Define a two-dimensional array of state transition tables state[0][0] = 0; state[0][weight[0]] = value[0]; //State transition using dynamic programming for (int i = 1; i < n; i++) { for (int j = 0; j <= w; j++) { //Don't put the ith item into the backpack because the weight state of this subscript already exists if (state[i - 1][j] >= 0) state[i][j] = state[i - 1][j]; } for (int j = 0; j <= w - weight[i]; j++) { //Put the i-th item into the backpack if (state[i - 1][j] >= 0) { int v = state[i - 1][j] + value[i]; //The maximum total value corresponding to the current state is always saved in the state transition array if (v > state[i][j + weight[i]]) state[i][j + weight[i]] = v; } } } //Output results int maxValue = -1, maxpos = -1; for (int j = 0; j <= w; j++) { if (state[n - 1][j] > maxValue) { maxValue = state[n - 1][j]; maxpos = j; } } return new int[]{maxpos, maxValue}; }
Similarly, you can also optimize the above code
/** * It also optimizes the space of the DP method above * Similarly, the state transition array here uses a one-dimensional array */ public int[] DPSlove2(int[] weight, int[] value, int n, int w) { int[] states = new int[w + 1]; //An array defined as a state transition //The first item also needs to be handled states[0] = 0; states[weight[0]] = value[0]; for (int i = 1; i < n; i++) { // dp for (int j = w - weight[i]; j >= 0; j--) { //Put the i-th item into the backpack //Put the i-th item into the backpack if (states[j] >= 0) { int v = states[j] + value[i]; //The maximum total value corresponding to the current state is always saved in the state transition array if (v > states[j + weight[i]]) states[j + weight[i]] = v; } } } //Output results int maxValue = -1, maxpos = -1; for (int i = w; i >= 0; i--) { if (states[i] > maxValue) { maxValue = states[i]; maxpos = i; } } return new int[]{maxpos, maxValue}; }
The longest common substring between two strings
Given two strings, the Longest Common Sequence of the two strings is solved. For example, string 1: BDCABA; String 2: ABCBDAB, then the longest common subsequence length of the two strings is 4, and the longest common subsequence is BCBA
dp state transition equation is
The code is as follows:
public int lcs(char[] a, char[] b) { int n = a.length, m = b.length; int[][] dp = new int[n + 1][m + 1]; for (int i = 1; i <= n; i++) { //Note here is < = No< for (int j = 1; j <= m; j++) { if (a[i - 1] == b[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } return dp[n][m]; }
The length of the longest increasing subsequence in a set of numbers
For example, the length of the longest increasing subsequence of 2, 9, 3, 6, 5, 1 and 7 is 4 (2,3, (6) 5,7)
From the meaning of the question, the maximum increasing subsequence of the current subscript a[i] is the largest plus 1 of the length of the rising subsequence of all previous elements smaller than him.
Therefore, the state transition equation is DP [i] = max (DP [J]) + 1 if a [i] > a [J] & & I > J
public int longestLenDP(int[] a) { int n = a.length; int[] dp = new int[n]; Arrays.fill(dp, 1); int max = 1; for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { if (a[j] < a[i]) dp[i] = Math.max(dp[i], dp[j] + 1); } max = Math.max(max, dp[i]); } return max; }