题目描述:
假设有排成一行的N个位置,记为1~N,N一定大于或等于2
开始时机器人在其中的M位置上(M一定是1~N中的一个)
如果机器人来到1位置,那么下一步只能往右来到2位置
如果机器人来到N位置,那么下一步只能往左来到N-1位置
如果机器人来到中间(非两端位置)位置,那么下一步可以往左走或者往右走
规定机器人必须走K步,最终能来到P位置(P也是1~N中的一个)的方法有多少种给定四个参数N、M、K、P,返回方法数
思路分析:
为了方便描述,我们将1位置认为是最左边,N位置认为是最右边,用cur代替M参数,rest 代替K参数
对于机器人来说,他的行进路线有三种: 当来到最左时,必须向右走;当来到最右时,必须向左走;当处于中间位置时,向左向右都可以
详解:
1. 定义 walk(N,cur,rest,P)方法:
该方法表示在N个位置下,走rest步,从cur位置到P位置的可行方法数
N:一共有N个位置(不变参数)
cur:当前位置(可变参数)
rest:剩余步数(可变参数)
P:目标位置(不变参数)
2. 对机器人的 路线进行分析:
bese case:rest=0 (即已经走完剩余步数),如果cur=P (即来到目标位置),那么可行方法数为1 ,方法的返回值为1,反之则返回0
来到1位置(最左):此时cur = 1,按照规定,此时只能往右移动,则 返回walk(N,2,rest-1,P),表示在2位置下,移动rest-1步到P位置的可行方法数
来到N位置(最右):此时cur = N,按照规定,此时只能往左移动,则 返回walk(N,N-1,rest-1,P),表示在N-1位置下,移动rest-1步到P位置的可行方法数
中间位置(非最左 / 最右):此时 有两种路线选择:
向左走:此时只需要将当前位置和剩余步数做出改变即可,也就是参数中的两个可变参数,此时返回 walk(N,cur-1,rest-1,P)
向右走:此时同样只需要将当前位置和剩余步数做出改变即可,返回 walk(N,cur+1,rest-1,P)
此时我们的路线既有向左,也有向右,两边都可以得到可行方法数,所以只需要将二者相加,综合起来也就是返回 walk(N,cur-1,rest-1,P)+walk(N,cur+1,rest-1,P)
这道题的分析至此,接下来看暴力递归代码:
public static int way(int N, int M, int K, int P) {
//无效情况
if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
return 0;
}
return walk(N, M, K, P);
}
private static int walk(int N, int cur, int rest, int P) {
//如果剩余步数为0,base case
if (rest == 0) {
//看是否已经到达目标位置
return cur == P ? 1 : 0;
}
//来到最左边
if (cur == 1) {
//向右走哦
return walk(N, 2, rest-1, P);
}
//来到最右边
if (cur == N) {
//向左走哦
return walk(N, N-1, rest-1, P);
}
//中间位置,返回向左和向右的可行方案数之和
return walk(N, cur-1, rest-1, P)
+walk(N, cur+1, rest-1, P);
}
接下来我们看是否有重复计算
来看方法的定义:
walk(N,cur,rest,P)方法:
N:一共有N个位置(不变参数)
cur:当前位置(可变参数)
rest:剩余步数(可变参数)
P:目标位置(不变参数)
可以看出, N与P都是固定参数, 可变参数有cur与rest
也就是说,传入不同的可变参数,得到的结果不同,即 结果只与可变参数cur与rest有关, N与P固定参数只起到了限制作用
现在我们的 walk(N,cur,rest,P)方法用 f(cur,rest)来代替,因为只与这两个变量有关!
假设现在有6个位置,机器人初始位置在3,走4步到5位置
可以看出,过程中绝对会产生重复解!
如果我们将重复解第一次出现时将它放进一个缓存里,下次就可以直接调用!
代码将做出以下修改!
public static int way(int N, int M, int K, int P) {
//无效情况
if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
return 0;
}
//因为只有两个可变参数,所以用二维数组作缓存
int[][] dp = new int [N+1][K+1];
//先把所有情况设置为-1
for (int i=0; i <= N; i++) {
for (int j=0; j <= K; j++) {
dp[i][j] = -1;
}
}
return walk(N, M, K, P, dp);
}
private static int walk(int N, int cur, int rest, int P, int[][] dp) {
//如果对应的值出现过,那么可以直接从缓存里面取出,不必递归
if (dp[cur][rest] != -1) {
return dp[cur][rest];
}
//剩下的情况就是没有在缓存里出现过
//如果剩余步数为0,base case
if (rest == 0) {
//看是否已经到达目标位置
//每一步先存入缓存,再返回
dp[cur][rest] = (cur==P?1 : 0);
return dp[cur][rest];
}
//来到最左边
if (cur == 1) {
//向右走哦
//每一步先存入缓存,再返回
dp [cur][rest] = walk(N, 2, rest-1, P, dp);
return dp[cur][rest];
}
//来到最右边
if (cur == N) {
//向左走哦
//每一步先存入缓存,再返回
dp [cur][rest] = walk(N, N-1, rest-1, P, dp);
return dp[cur][rest];
}
//中间位置,返回向左和向右的可行方案数之和
//每一步先存入缓存,再返回
dp [cur][rest] = walk(N, cur-1, rest-1, P, dp)
+ walk(N, cur+1, rest-1, P, dp);
return dp[cur][rest];
}
这种题也叫记忆化搜索,将第一次出现的解存入缓存中(不一定是二维数组,HashMap等都可以),下次遇到时可以直接调用!