寻友之旅|码上掘金
当青训营遇上码上掘金。
小青家与小码家住在一条直线上,小青在N点,而小码在K点。小码十分着急,他很想知道小青什么时候会来;小青却发了愁,因为她既可以选择步行,又可以坐公交;坐公交可以一次性从X移动到2X位置,而步行每次只能行走一格。小青想知道自己该怎么行动,才能最快到达小码家呢?
解题灵感
现实生活中,我们都坐过公交车出行。上中学时,周一早上赶公交,原本要乘坐的公交车通常要等很久才来。有时候为了避免迟到,我会选择步行一段距离,到附近一个更大的公交站,可以有更多公交车进行选择。同时,换乘公交也是常事,有时两个公交的换乘站并非同站换乘,而是需要走动一定距离到另一个换乘站去换乘。下车时,由于我的学校在两个车站中间,我也会考虑是提前一站下车,还是多坐一站再往回走。
如果考虑这一道题的要求,我们会发现其实和平日坐公交车需要做出的选择是一样的。在上车前,我们会决定是否在当前站坐公交,还是步行一段距离;上车后,我们在每一站都会决定是否下车(即使我们坐车时候一般不会在中间站考虑下车,但实际上是因为我们根据经验已经决定好在某一站下车,因此也自动决定好在其他站不下车,实际还是做了选择);下车后,我们也还需要步行到目的地。所以实际上,我们在整个过程中一直在做三个决策:向前走,向后走,上车。而上一步的选择结果会影响下一步,所以这是一个递归问题。
问题解析
我们使用递归的方法来解决这个问题。
首先,小青在每一点X处都有3种操作,她可以往回走到X-1,可以往前走一格到X+1,也可以坐公交移动到2X处。因此我们需要找到在这三种选择中用时最短的方法。假设recursion(X,t)是我们的递归函数,X为当前位置,t为已经经过的时间:
- 往回走,变为recursion(X-1, t+1);
- 往前走,变为recursion(X+1, t+1);
- 坐公交,变为recursion(2*X, t+1);
需要注意的关键一点在于,小青在移动的过程中不应该走回到曾经走过的位置,因此我们需要一个list来保存她走过的位置。我们还需要把0加入走过的位置,避免小青一直向左走到负数数轴上。所以我们维护一个visited数组,每次更新时会更新这个数组,从而保证小青不会回到自己曾经到过的地方,而陷入死循环。
我们在每次小青做出选择时,先假设她走到了对应的X-1,X+1和2X的位置,把这些位置存入visited数组中,然后获取对应移动后到达终点所需要的时间。随后,我们恢复visited数组到这个假设操作之前,然后比较3种移动所需要的时间,返回最小的一个时间,然后把当前走的位置放入visited数组。由于如果走过了的话只有步行一格格回到终点一种可能性,我们的算法一定会收敛,获得最终结果。
下面是利用递归解决这个问题的代码。
代码整理
//main程序,获取N和K的位置
public static void main(String[] args){
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int k = in.nextInt();
//创建一个visited list记录已经走过的位置
ArrayList<Integer> visited = new ArrayList<Integer>();
//避免走到负数
visited.add(0);
//初始位置
visited.add(n);
//开始递归
System.out.println(getTime(n,k,0,visited));
in.close();
}
public static int getTime(int n, int k, int time, ArrayList<Integer> visited) {
//base case,到达,返回当前所花时间
if (n==k) {
return time;
}else if(n>k) {
//走过了,只能一格一格倒回去
return getTime(n-1, k, time+1, visited);
}else {
//创建很大的时间
int walkMinus = 1000000;
int walkPlus = 1000000;
int bus = 1000000;
//如果没去过当前点-1的位置
if(!visited.contains(n-1)) {
//创建一个list存储当前去过的位置
ArrayList<Integer> temp = new ArrayList<Integer>();
temp.addAll(visited);
//假装移动到当前位置-1的地方
visited.add(n-1);
//获取这个移动所需要的时间
walkMinus = getTime(n-1, k, time+1,visited);
//把visited还原成原来的状态,从temp拷贝回来
visited = new ArrayList<Integer>();
visited.addAll(temp);
}
//如果没去过当前点+1的位置
if(!visited.contains(n+1)) {
//存储当前去过的位置
ArrayList<Integer> temp = new ArrayList<Integer>();
temp.addAll(visited);
//假装移动到当前位置+1的地方
visited.add(n+1);
//获取这个移动需要的时间
walkPlus = getTime(n+1,k,time+1, visited);
//还原visited到原来的状态
visited = new ArrayList<Integer>();
visited.addAll(temp);
}
//如果没去过当前点x2的地方
if(!visited.contains(2*n)) {
//同样,存储当前去过的位置
ArrayList<Integer> temp = new ArrayList<Integer>();
temp.addAll(visited);
//假装移动到当前位置*2的地方
visited.add(2*n);
//获取这个移动需要的时间
bus = getTime(2*n, k, time+1, visited);
//还原visited到原来的状态
visited = new ArrayList<Integer>();
visited.addAll(temp);
}
//寻找上述3种情况中最小的一种
if(bus<walkPlus && bus<walkMinus) {
//坐车最小,把坐车的目的地加进visited中
visited.add(2*n);
//返回坐车的时间
return bus;
}else if(walkMinus<walkPlus){
//往回走一格最小,把往回走一格加进visited中
visited.add(n-1);
//返回往回走一格的时间
return walkMinus;
}else {
//往前走一格最小,把往前走一格加进visited中
visited.add(n+1);
//返回往前走一格的时间
return walkPlus;
}
}
}