Explanation of dynamic programming (DP) and related classical problems

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;
    }

Tags: Algorithm Interview Dynamic Programming

Posted on Tue, 05 Oct 2021 13:27:03 -0400 by CowGuy