机器人走路(从递归到动态规划)

151 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

题目

给定一个数字N,表示的是有N个位置,现在再来一个数字K,表示机器人现在处于K位置,再来一个数字P表示的是机器人要去的位置,当然N表示的这些位置都在一条直线上面。现在再来一个数字K表示的是走的步数,现在机器人可以往左也可以往右,不过到最左边的时候只能往右边走,走到最右边的时候只能往左边走。

递归实现——代码

public static int ways(int N , int start , int P , int K){
    if (N < 2 || start < 1 || start > N || P < 1 || P > N || K < 1) {
        return -1;
    }
    return process(start, K, P, N);
}
public static int process(int cur, int rest, int P, int N) {
    if(rest == 0){
        return P == cur ? 1 : 0;
    }
    if(cur == 1){
        return process(cur + 1 , rest - 1 , P , N);
    }
    if(cur == N){
        return process(cur - 1 , rest - 1 , P , N);
    }
    int left = process(cur - 1 , rest - 1 , P , N);
    int right = process(cur + 1 , rest - 1 , P , N);
    return left + right;
}

递归实现——思路

我们看着代码一步步来

先是一个函数,表示的就是引用函数,这个只要看一下健壮性考虑的地方就可以了。

第二个函数就是这个方法的灵魂了。我们先介绍一下各个参数的含义

cur: 现在处在的位置是在哪里

rest: 还有多少步要走

P: 表示要去的位置是什么

N: 表示的是有多少个位置

我们看第一个if,这个地方表示的是当我要走的步数为0的时候,我要考虑现在的位置是否在目标位置,如果在的话就返回1,表示这个走法是一个可行的方法。如果不是就返回0,表示这个走法是不行的。

我们看第二个if,这个地方表示当走到最左边的时候,我们就要往右边走了。

我们看到第三个if,这个地方表示的是当走到最右边的时候,我们就要往左边走了。

我们看到最后,我们没有什么可以考虑的普通位置的时候,我们可以往左边走,也可以往右边走,这个时候我们就用了两个变量来保存,之后再返回他们相加之后的数字就可以了。

上面就是整个递归实现的思路,然后我们来看动态规划了。

动态规划实现——代码

public static int ways(int N, int start, int P, int K) {
    if (N < 2 || start < 1 || start > N || P < 1 || P > N || K < 1) {
        return -1;
    }
    int[][] dp = new int[K + 1][N + 1];
    dp[0][P] = 1;
    for(int i = 1;i<=K;i++){
        dp[i][1] = dp[i - 1][2];
        for(int j = 2;j<N;j++){
            dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
        }
        dp[i][N] = dp[i - 1][N - 1];
    }
    return dp[K][start];
}

动态规划实现——思路

我们来看看各个参数的含义N表示的是有多少个位置,start表示的是开始的位置,P表示的是目标位置,K表示的是有多少步可以走

我们来看这个代码,首先最前面的健壮性考虑大家看看就好了。这个比起后面的不是很重要

之后我们创建了一个dp数组,我们之后的动态规划都是在上面执行的。

我们来看看dp长度的设置。

第一个是K + 1,表示的就是步数。+1就是为了防止溢出

第二个是N + 1,表示的是位置,我们这边的+1是排除0的干扰就是我们不想用到0

之后我们让dp[0][P] = 1;这个是为什么呢?

我们看看上面递归实现的代码我们就知道了。

if(rest == 0){
    return P == cur ? 1 : 0;
}

我们看看这个代码,是不是表示当步数为0的时候,如果走到的位置是P那么就是1.

那么dp[0][P] 表示的是当步数为0的时候,位置是P的点。那没有在P上的就是0,因为数组默认为0所以就不用设置了。

之后我们到了两个for的循环里面。

因为我们已经弄好了dp[0]这一行了。那我们之后就可以从dp[1]开始。

dp[i][1] = dp[i - 1][2];

这个是什么意思呢?

就是当我们到1位置的时候,我们就要往右边走的意思。对应的递归代码就是这个

if(cur == 1){
    return process(cur + 1 , rest - 1 , P , N);
}

我们看到了第二个for

for(int j = 2;j<N;j++){ 
    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1]; 
}

这个是什么意思呢?

我们先看看dp[i - 1][j - 1]

这个表示的就是步数少一步,然后位置往左边走

dp[i - 1][j + 1]

这个表示的是步数少一步,位置往右边走

那我们看看dp[i][j]

这个表示的就是当前位置的大小是多少。

对应的递归代码是

int left = process(cur - 1 , rest - 1 , P , N);
int right = process(cur + 1 , rest - 1 , P , N);
return left + right;

跟着上面的思路我们就好解释这个了dp[i][N] = dp[i - 1][N - 1];

表示的是当走到最右边的时候我们就要往左边走一下的。

对应的递归代码就是

if(cur == N){
    return process(cur - 1 , rest - 1 , P , N);
}

最后我们要知道的是返回值是怎么知道用什么地方的?

我们还是参考递归代码

public static int ways(int N , int start , int P , int K){ 
    if (N < 2 || start < 1 || start > N || P < 1 || P > N || K < 1) { 
        return -1; 
    } 
    return process(start, K, P, N);
}

我们知道最后返回的地方,我们用到的就是start和K。

那我们动态规划的地方返回的也是return dp[K][start];

以上就是从递归到动态规划。