zoukankan      html  css  js  c++  java
  • 阿里笔试题(3.23)——走迷宫

    时间原因没参加23号的笔试,之后看了一下笔试题,第一道纯计算,主要是找到数学规律,另外注意中间取模防止溢出。第二道看了之后感觉有点头秃,还是自己平时刷题太少。网上看了看其他大佬的解法然后自己写了一下,这里总结一下。

    题目如下:

    给一个迷宫(1<=n,m<=500), 小明要从初始位置S到达目标位置E,支持五种操作,上/下/左/右/瞬移,瞬移一共可以用5次,每次可以从(x,y) 移动到(n-1-x, m-1-y), 问小明从S到E的最短时间,如果不能到达输出-1。

    示例:

    输入:

    4 4
    #S..
    E#..
    ....
    ....

    输出:

    4

    解释:

    首先从S(0,1)瞬移到(3,2),再网上一步(2,2),再往右一步(2,3),最后瞬移到达E(1,0),共4步。

    1. 不考虑瞬移操作

    如果不考虑瞬移操作,就是一般的迷宫最短路径题,用BFS搜索即可,并且BFS能够保证找到的第一个解就是最优解。解法如下:

     import java.util.LinkedList;
     import java.util.Queue;
     import java.util.Scanner;
     
     public class Main{
         public static void main(String[] args){
             Scanner sc = new Scanner(System.in);
             while(sc.hasNext()){
                 int n = sc.nextInt();
                 int m = sc.nextInt();
                 char[][] migong = new char[n][m];
                 int[] begin = new int[2];
                 int[] end = new int [2];
                 for(int i =0; i < n; i++){
                     String s = sc.next();
                     migong[i] = s.toCharArray();
                     if(s.contains("S")){
                         begin[0] = i;
                         begin[1] = s.indexOf('S');
                     }
                     if(s.contains("G")){
                         end[0] = i;
                         end[1] = s.indexOf('G');
                     }
                 }
                 int shortestPath = getPath(migong, begin, end);
                 System.out.println(shortestPath);
             }
         }
         
         private static int getPath(char[][] migong, int[] begin, int[] end){
             // 四个移动方向,dx和dy一一对应
             int[] dx = {1, 0, -1, 0};
             int[] dy = {0, 1, 0, -1};
             // path存储当前点到起点的最短路径
             int[][] path = new int[migong.length][migong[0].length];
             Queue<int[]> queue = new LinkedList<int[]>();
             for(int i = 0; i < migong.length; i++){
                 for(int j = 0; j < migong[0].length; j++){
                     path[i][j] = Integer.MAX_VALUE; // 初始化所有点的最短路径为最大值
                 }
             }
             queue.offer(begin);   // 起始点放入队列
             path[begin[0]][begin[1]] = 0;
             // 一直循环直到队列为空或是遍历到出口
             while(!queue.isEmpty()){
                 int[] curr = queue.poll();    // 取出队列最前端的点
                 // 遍历每一个方向
                 for(int i = 0; i < 4; i++){
                     int x = curr[0] + dx[i];  
                     int y = curr[1] + dy[i];
                     // 判断是否可以移动到当前点
                     if(x >= 0 && x < migong.length && y >= 0 && y < migong[0].length && migong[x][y] != '#' && path[x][y] == Integer.MAX_VALUE){
                         path[x][y] = path[curr[0]][curr[1]] + 1;  // 可以移动那么当前点的路径长度就是上一步加1
                         if(x == end[0] && y == end[1]){   // 到达出口
                             return path[x][y];
                         }
                         queue.offer(new int[]{x, y});  // 当前点入队
                     }
                 }
             }
             return -1;  // 队列为空但是还没到达出口,表明无法到达
         }
     }

    代码参考自这篇博客,原来的代码在每次取出队列最前端的点后判断是不是出口。但是似乎这样会增加一些不必要的计算(自己的想法,不知道合不合理),例如在某一点,其上下左右四个点中有一个就是出口点,那么在for循环中每次向某个方向移动后就判断是不是出口,这样避免了需要将四个点都入队然后继续计算。

    2. 考虑可以瞬移的操作

    回到阿里这道笔试题,这个瞬移的操作我想了半天没想明白怎么搞。菜哭。然后看了一下大佬们的解法,自己写了一下:

     import java.util.LinkedList;
     import java.util.Queue;
     import java.util.Scanner;
     
     public class Main{
         public static void main(String[] args){
             Scanner sc = new Scanner(System.in);
             while(sc.hasNext()){
                 int n = sc.nextInt();
                 int m = sc.nextInt();
                 char[][] migong = new char[n][m];
                 int[] begin = new int[2];
                 int[] end = new int [2];
                 for(int i =0; i < n; i++){
                     String s = sc.next();
                     migong[i] = s.toCharArray();
                     if(s.contains("S")){
                         begin[0] = i;
                         begin[1] = s.indexOf('S');
                     }
                     if(s.contains("G")){
                         end[0] = i;
                         end[1] = s.indexOf('G');
                     }
                 }
                 int shortestPath = getPath(migong, begin, end);
                 System.out.println(shortestPath);
             }
             sc.close();
         }
         
         private static int getPath(char[][] migong, int[] begin, int[] end){
             // 四个移动方向,dx和dy一一对应
             int[] dx = {1, 0, -1, 0};
             int[] dy = {0, 1, 0, -1};
             // 用三维数据path存储从起点到某一点的最短路径和飞行次数
             // 其中path[i][j][0]存储起点到[i][j]的最短路径
             // path[i][j][1]存储起点到[i][j]的飞行次数
             int[][][] path = new int[migong.length][migong[0].length][2];
             Queue<int[]> queue = new LinkedList<int[]>();
             for(int i = 0; i < migong.length; i++){
                 for(int j = 0; j < migong[0].length; j++){
                     path[i][j][0] = Integer.MAX_VALUE; // 初始化所有点的最短路径为最大值
                     path[i][j][1] = 0;  // 初始化所有点的飞行次数为0
                 }
             }
             queue.offer(begin);   // 起始点放入队列
             path[begin[0]][begin[1]][0] = 0;
             // 一直循环直到队列为空或是遍历到出口
             while(!queue.isEmpty()){
                 int[] curr = queue.poll();    // 取出队列最前端的点
                 if(curr[0] == end[0] && curr[1] == end[1]){   // 到达出口
                     return path[curr[0]][curr[1]][0];
                 }
                 
                 // 首先遍历每一个方向,单步移动
                 for(int i = 0; i < 4; i++){
                     int x = curr[0] + dx[i];  
                     int y = curr[1] + dy[i];
                     // 判断是否可以移动到当前点
                     if(x >= 0 && x < migong.length && y >= 0 && y < migong[0].length && migong[x][y] != '#' && path[x][y][0] == Integer.MAX_VALUE){
                         path[x][y][0] = path[curr[0]][curr[1]][0] + 1;  // 可以移动那么当前点的路径长度就是上一步加1
                         queue.offer(new int[]{x, y});  // 当前点入队
                     }
                 }
                 
                 // 如果当前点的飞行次数小于5次,则尝试从当前点飞行到其对称点
                 if(path[curr[0]][curr[1]][1] < 5){
                     // 当前点对称位置坐标
                     int flyX = migong.length - 1 - curr[0];
                     int flyY = migong[0].length - 1 - curr[1];
                     // 判断当前点对称位置是否能到达
                     if(flyX >= 0 && flyX < migong.length && flyY >= 0 && flyY < migong[0].length && migong[flyX][flyY] != '#' && path[flyX][flyY][0] == Integer.MAX_VALUE){
                         path[flyX][flyY][0] = path[curr[0]][curr[1]][0] + 1;  // 飞行到对称点,路径长度+1
                         path[flyX][flyY][1] = path[curr[0]][curr[1]][1] + 1;  // 到达对称点的飞行次数就是当前点的飞行次数+1
                         queue.offer(new int[]{flyX, flyY});  // 对称点入队
                     }
                 }
             }
             return -1;  // 队列为空但是还没到达出口,表明无法到达
         }
     }

    主要是两个变化,首先将原本存储路径的二维数组  path  变成三维数组,第三维的长度为2,其中  path[i][j][0]  存储的就是点ij到出发点的最短距离,即原本的  path[i][j]  ,  path[i][j][1]  存放到达当前点的瞬移次数;然后就是,每次在向四个方向都移动之后,再找到当前点的对称点,看是不是可以瞬移过去,可以的话就更新其对称点的  path  值。

    代码是参考了其他两位大神的,在此表示感谢。因为没机会在刷题平台上测试,只是在自己的测试用例上通过了所以不知道有没有什么问题,欢迎大家批评指正。明天参加阿里的笔试,希望别被虐的太惨,大家都加油啊!

    参考资料

    1.  https://blog.csdn.net/c_yejiajun/article/details/86655430

    2.   https://www.nowcoder.com/discuss/389778?type=1

    3.  https://www.cnblogs.com/xlsryj/p/12558114.html

  • 相关阅读:
    创业之死:97%的创业失败是因为…… . 分类: 项目管理 2014-06-18 17:56 315人阅读 评论(1) 收藏
    linux下的APK反编译软件及过程介绍 . 分类: arm-linux-Ubuntu 2014-06-18 17:51 400人阅读 评论(0) 收藏
    g++基本用法 分类: arm-linux-Ubuntu 2014-06-18 17:50 414人阅读 评论(0) 收藏
    更改 vc6 各窗口字体 (zz)
    程序员编程技术迅速提高终极攻略 (zz)
    A browser for WinCE/Windows base WebKit. (zz)
    输入一个十进制的数到dx_ax,然后十六进制转十进制输出
    汇编判断语句
    用结构体指针存储数据__正序_逆序下的输入
    二叉排序树
  • 原文地址:https://www.cnblogs.com/rezero/p/12577852.html
Copyright © 2011-2022 走看看