机器人找位置——从暴力递归到动态规划

题目描述

假设有排成一行的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等都可以),下次遇到时可以直接调用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值