Violent recursion to dynamic programming (1)

The first two posts introduced the process of violent recursion, generally speaking, it is the use of natural wisdom + continuous attempts . This article will introduce how to convert violent recursion into dynamic programming.

Fibonacci sequence The
Fibonacci must be familiar. It is stipulated that the value of the first column is 1 and the value of the second column is 2, then the value of the seventh column is 13, and so on, the whole is a f(N) = f(N -1 ) + f(N - 2).
insert image description here
Then if you try to use violent recursion to solve it, the code is as follows:

public static int f(int N) {
    
    
        if (N == 1) {
    
    
            return 1;
        }
        if (N == 2) {
    
    
            return 1;
        }
        return f(N - 1) + f(N - 2);
    }

Such a few lines of code actually realize the Fibonacci sequence. If the value of f(7) is required at this time, if you expand it, it is actually the shape of a binary tree. f(7) depends on f(5) and f(6). f(6) depends on f(5), f(4) and so on. . . .
insert image description here
It can be seen from the drawing that if you want to obtain a value of f(7), you will rely on many methods, and some methods will be executed many times. If you have a table structure at this time, you can "cache" the previous results. When I encounter it later, I get the value directly. Is it much more convenient, use O ( N ) O(N)O ( N ) time complexity can solve this problem.
This "cache" is dynamic programming. Come to a specific topic to consolidate:

Topic
Suppose there are N positions lined up in a row, recorded as 1 ~ N, N >= 2, there is a robot, and at the beginning, the robot
is at the start position (start is the one in the middle of 1 ~ N).
If the robot is currently at position 1, the next step can only be to the right to position 2.
If the robot is at position N, then the next step can only go left to position N - 1.
If in the middle, you can move left and right. It is stipulated that the robot must take K steps, and finally come to the target position (target is also one of 1 ~ N).
Ask the robot to take K steps from start to the target position, how many ways are there in total.
insert image description here
As shown, suppose the current starting point is at 2, the end point is at 4, a total of 4 positions, a total of 4 steps, there are several ways to get there: a total of three

  1. 2 -> 1 -> 2 - >3 -> 4
  2. 2 -> 3 -> 4 -> 3 -> 4
  3. 2 -> 3 -> 2 -> 3 -> 4

violent recursion

  1. According to the topic analysis, confirm the base case. When the step is 0, it means that there are no steps to go, and then return.
  2. If cur = 1, then the next step can only go to the right.
  3. If cur = N, then the next step can only go to the left.
  4. Otherwise, in the middle, left and right can be used. The method of going to the left to reach the target + the method of going to the right to reach the target is the total number of methods.

Code
parameter cur: the current position of the robot step: the number of remaining steps target: the target position N: a total of N positions
K: a total of how many steps

 	//方法返回机器人从cur出发,走step步,到达target的方法数
    public static int ways(int cur, int K, int target, int N) {
    
    
        return process(cur, K, target, N);
    }

    public static int process(int cur, int step,int target, int N) {
    
    
    	//当步数为0时,看当前位置是否在目标位置,如果在,则方法数 + 1,否则认为没走到为0
        if (step == 0) {
    
    
            return cur == target ? 1 : 0;
        }
        //无论怎么走,每走一步 step 一定 -1
		//当前位置为1时,必须向右,所以下一步会是在2位置
        if (cur == 1) {
    
    
            return process(2, step - 1, target, N);
        }
        //当前位置为N时,必须向左,所以下一步在N -1位置
        if (cur == N) {
    
    
            return process(N - 1, step - 1, target, N);
        }
        //否则,在中间,可能向左走,也可能向右走
        return process(cur + 1, step - 1, target, N) + process(cur - 1, step - 1, target, N);
    }

optimization
How to use violent recursion to optimize into dynamic programming? One of the most important points is to see if there is any repeated connection according to the calling process. If there are repeated solutions, it can definitely be optimized into dynamic programming..
From the above code, it can be analyzed that what are the factors that affect the results of the ways method?
N is fixed, and the target is also fixed. In the above code, is it only the change of the current position of cur and the step is constantly decreasing. So, the parameters that affect the overall result are the current position and the number of steps.
Let's unfold the process. For example, now start = 7, target = 15, step = 10, starting from position 7 to position 15, it takes 10 steps to see how it moves.
insert image description here
7 is the middle position. After starting, you can walk on both sides. When the current position is 7, the remaining steps are 8. The sub-process has repeated calls, which means that it can be optimized from violent recursion to dynamic programming. Don't care about the previous call at this time.
Because the current positions of the two circled places are both 7, the number of remaining steps is 8, and the target is fixed at 15, so the number of ways to reach the target must be the same at this time. So don't care if it's 7 -> 6 -> 7 or 7 -> 8 -> 7.
It can be analyzed from this that cur and step are the keys to determine the state. As long as these two are determined, the result can be determined.
The key is found, and a two-dimensional array is created according to the key to represent the cached table. If you come to the current location and the number of remaining steps is still the same, then the number of methods to reach the target must also be the same. If it exists in the cache table, you can directly obtain it through the cache table.

the code

	//cur范围:0 ~ N
    //step范围:0 ~ K
    public static int ways2(int cur, int K, int target, int N) {
    
    
        //创建一个N + 1和 K + 1返回的数组,保证到任何位置都能囊括进去
        int[][] dp = new int[N + 1][K + 1];
        for (int i = 0; i <= N; i++) {
    
    
            for (int j = 0; j <= K; j++) {
    
    
                dp[i][j] = -1;
            }
        }
        return process2(cur, K, target, N, dp);
    }

    public static int process2(int cur, int step, int target, int N, int[][] dp) {
    
    
        //不等于 -1 说明 之前计算过当来到cur位置时,剩余step 到达target的方法数
        if (dp[cur][step] != -1) {
    
    
            return dp[cur][step];
        }
        //走到这,说明没算过
        int ans = 0;
        if (step == 0) {
    
    
            ans = cur == target ? 1 : 0;
        } else if (cur == 1) {
    
    
            ans = process2(2, step - 1, target, N, dp);
        } else if (cur == N) {
    
    
            ans = process2(N - 1, step - 1, target, N, dp);
        } else {
    
    
            ans = process2(cur + 1, step - 1, target, N, dp) + process2(cur - 1, step - 1, target, N, dp);
        }
        //记录当前位置到达target的方法数。
        dp[cur][step] = ans;
        return ans;
    }

At this time, the code has been optimized by "stupid cache", and the number of methods in each step is recorded through the dp table. This top-down dynamic programming is called "memorized search". In essence, each branch is Search, form memorization through the cache table, use space for time, and use a cache table to exchange for more time.

How to optimize the above code again?
The above constructs a two-dimensional array cache table based on the current position of cur and the remaining number of steps. This two-dimensional array contains all the results of each step in the given cur and step. Can you try to draw this dp table? ? Assume that the current robot is at position 2, there are 5 positions in total, and it takes 6 steps to walk, and the goal is to go to position 4.
So cur = 2 K = 6 N =5 target = 4 , the picture drawn is like this.

The row is the current location, and the column is the number of steps. Because it is impossible to reach row 0, the location of row 0 is marked x. The initial position is 2, there are 6 steps left, marked with a star.
I just need to complete the graph and fill the grid with the current position and the number of result methods at each step. Is it possible to know the number of steps in positions 2-6 directly from the table.
insert image description here
So how to improve this table? Starting from the initial violent recursive code, there are some base cases that will give us the answer directly. According to the code logic, it is perfected line by line.

 public static int process(int cur, int step, int target, int N) {
    
    
        if (step == 0) {
    
    
            return cur == target ? 1 : 0;
        }

        if (cur == 1) {
    
    
            return process(2, step - 1, target, N);
        }
        if (cur == N) {
    
    
            return process(N - 1, step - 1, target, N);
        }
        return process(cur + 1, step - 1, target, N) + process(cur - 1, step - 1, target, N);
    }

Let’s start with the base case. If my remaining steps are 0, if the position of cur is on the target, does it mean that there is a method that can be reached, and the number of other methods that are not on the target position is 0, so the first column it came out.
insert image description here
Continue to look down, the current state is cur and step (the parameters are passed in), then if the current position cur = 1, according to the meaning of the question, the next step can only move to 2 positions, and each time you move step - 1, so if At this time, in the case of cur = 1, step = 1, it depends on the result of cur = 2, step = 0. So the dependency relationship of cur at any position on the first line is like this.
insert image description here
Continue down, if the current cur = N, then the next step can only move to the left (N -1), and also move step - 1 every time, so the dependency of cur at any position in the N line is like this.

Well, now only the general position is left, assuming that cur = 3 at this time, look at the code, you can move left or right. So the dependencies at this point are like this.
insert image description here
According to the code, if cur = 3 at this time, whether to add the dependent results, then whether the dp table is complete at this time.
insert image description here
It can be seen that when cur = 2 and step = 6, a total of 13 method solutions can reach the position of target = 4. At this time, if the dp table is directly constructed, can the results be directly obtained according to the variables cur and step?

Code
The entire dynamic programming code is very concise.

public static int ways3(int cur, int K, int target, int N) {
    
    
        int[][] dp = new int[N + 1][K + 1];
		//先将对应target位置标1
		//初始化int[][]默认值都是0,所以其他值不用管
        dp[target][0] = 1;
		//按列遍历
        for (int step = 1; step <= K; step++) {
    
    
        	//先将第一列的值确定
            dp[1][step] = dp[2][step - 1];
            //因为我单独遍历N行时的值,所以这个遍历到 N -1即可
            for (int j = 2; j < N; j++) {
    
    
            	//按照依赖关系,当前位置依赖左上和左下的位置
                dp[j][step] = dp[j - 1][step - 1] + dp[j + 1][step - 1];
            }
            //再将最后一列的值确定
            dp[N][step] = dp[N - 1][step - 1];
        }

        return dp[cur][K];
    }

Guess you like

Origin blog.csdn.net/weixin_43936962/article/details/132528726