zoukankan      html  css  js  c++  java
  • 🌞LCP 13. 寻宝

    2020.7.29 LeetCode

    好难啊

    题目描述

    我们得到了一副藏宝图,藏宝图显示,在一个迷宫中存在着未被世人发现的宝藏。

    迷宫是一个二维矩阵,用一个字符串数组表示。它标识了唯一的入口(用 'S' 表示),和唯一的宝藏地点(用 'T' 表示)。但是,宝藏被一些隐蔽的机关保护了起来。在地图上有若干个机关点(用 'M' 表示),只有所有机关均被触发,才可以拿到宝藏。

    要保持机关的触发,需要把一个重石放在上面。迷宫中有若干个石堆(用 'O' 表示),每个石堆都有无限个足够触发机关的重石。但是由于石头太重,我们一次只能搬一个石头到指定地点。

    迷宫中同样有一些墙壁(用 '#' 表示),我们不能走入墙壁。剩余的都是可随意通行的点(用 '.' 表示)。石堆、机关、起点和终点(无论是否能拿到宝藏)也是可以通行的。

    我们每步可以选择向上/向下/向左/向右移动一格,并且不能移出迷宫。搬起石头和放下石头不算步数。那么,从起点开始,我们最少需要多少步才能最后拿到宝藏呢?如果无法拿到宝藏,返回 -1 。

    示例

    示例一:
    输入: ["S#O", "M..", "M.T"]
    
    输出:16
    
    解释:最优路线为: S->O, cost = 4, 去搬石头 O->第二行的M, cost = 3, M机关触发 第二行的M->O, cost = 3, 我们需要继续回去 O 搬石头。 O->第三行的M, cost = 4, 此时所有机关均触发 第三行的M->T, cost = 2,去T点拿宝藏。 总步数为16。 
    
    ![](https://img2020.cnblogs.com/blog/1922094/202007/1922094-20200729100707468-980981759.png)
    
    
    示例二:
    输入: ["S#O", "M.#", "M.T"]
    
    输出:-1
    
    解释:我们无法搬到石头触发机关
    
    示例三:
    输入: ["S#O", "M.T", "M.."]
    
    输出:17
    
    解释:注意终点也是可以通行的。
    
    
    
    

    题解

    class Solution {
         private static final int MAX_VALUE = Integer.MAX_VALUE/2;
        
        static class Point{
            int x;
            int y;
            int dis;
            public Point(int x, int y) {
                this.x = x;
                this.y = y;
            }
    
            public Point setDis(int dis) {
                this.dis = dis;
                return this;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                Point point = (Point) o;
                return x == point.x &&
                        y == point.y;
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(x, y);
            }
        }
        int[] sToMdis;
        int[][] mToMdis;
        int[] sToOdis;
        int[][] mToOdis;
        int[][] oToMdis;
        int[] mToTdis;
        Map<Point, Integer> pointToIndex = new HashMap<>();
        char[][] map;
        public int minimalSteps(String[] maze) {
            map = new char[maze.length+2][maze[0].length()+2];
            for(char[] row : map) {
                Arrays.fill(row, '#');
            }
            int oLen = 0;
            int mLen = 0;
            Point start = null;
            Point end = null;
            List<Point> oList = new ArrayList<>();
            List<Point> mList = new ArrayList<>();
            for(int y = 0;y<maze.length;y++) {
                for(int x = 0; x < maze[0].length();x++) {
                    map[y+1][x+1] = maze[y].charAt(x);
                    Point p = new Point(x+1, y+1);
                    if (map[y+1][x+1] == 'O') {
                        oList.add(p);
                        pointToIndex.put(p, oLen++);
                    } else if (map[y+1][x+1] == 'M') {
                        mList.add(p);
                        pointToIndex.put(p, mLen++);
                    }else if(map[y+1][x+1] == 'S') {
                        pointToIndex.put(p, 0);
                        start = new Point(x+1,y+1);
                    }else if (map[y+1][x+1] == 'T') {
                        pointToIndex.put(p,0);
                        end = p;
                    }
                }
            }
    
            Map<Integer,Integer> sToTMap = bfs(start.x, start.y, 'T');
            // 如果s到不了T,直接返回-1
            if (sToTMap.isEmpty()) {
                return -1;
            }
    
            // 如果M的个数为0,那么直接返回S到T的距离
            if (mLen == 0) {
                return sToTMap.get(0);
            }
    
            // 计算s到所有O的距离
            sToOdis = new int[oLen];
            Arrays.fill(sToOdis, MAX_VALUE);
            Map<Integer,Integer> sToOMap = bfs(start.x, start.y, 'O');
            // 如果s连1个O都到不了,肯定不行。
            if (sToOMap.isEmpty()) {
                return -1;
            }
            for (Map.Entry<Integer, Integer> entry : sToOMap.entrySet()) {
                sToOdis[entry.getKey()] = entry.getValue();
            }
    
            // 如果s到不了所有M,则也不行
            Map<Integer,Integer> sToMMap = bfs(start.x, start.y, 'M');
            if (sToMMap.size() < mLen) {
                return -1;
            }
    
            // 计算所有o到M的距离
            oToMdis = new int[oLen][mLen];
            for (int[] oom : oToMdis) {
                Arrays.fill(oom, MAX_VALUE);
            }
            int i = 0;
            for (Point op : oList) {
                Map<Integer,Integer> oToMMap = bfs(op.x, op.y, 'M');
                if (oToMMap.isEmpty()) {
                    i++;
                    continue;
                }
                for (Map.Entry<Integer, Integer> entry : oToMMap.entrySet()) {
                    oToMdis[i][entry.getKey()] = entry.getValue();
                }
                i++;
            }
            
            mToOdis = new int[mLen][oLen];
            mToTdis = new int[mLen];
            i = 0;
            // 计算所有m到O的距离,和所有m到T的距离
            for (Point mp : mList) {
                Map<Integer,Integer> mToOMap = bfs(mp.x, mp.y, 'O');
                // 有一个M找不到任何和O的路径,那么就是错误的
                if (mToOMap.isEmpty()) {
                    return -1;
                }
                for (Map.Entry<Integer, Integer> entry : mToOMap.entrySet()) {
                    mToOdis[i][entry.getKey()] = entry.getValue();
                }
                // 有一个找不到T,那也是错误的
                Map<Integer,Integer> mToTMap = bfs(mp.x, mp.y, 'T');
                if (mToTMap.isEmpty()) {
                    return -1;
                }
                mToTdis[i] = mToTMap.get(0);
                i++;
            }
            sToMdis = new int[mLen];
            Arrays.fill(sToMdis, MAX_VALUE);
            mToMdis = new int[mLen][mLen];
            for (int[] mmm : mToMdis) {
                Arrays.fill(mmm, MAX_VALUE);
            }
    
            for (int m = 0; m < mLen;m++) {
                for (int o = 0; o < oLen;o++) {
                    // 计算s到各m且至少经过1个O的最小距离
                    int dis = sToOdis[o] + oToMdis[o][m];
                    if (dis < sToMdis[m]) {
                        sToMdis[m] = dis;
                    }
    
                    // 计算m到各mEnd且至少经过1个O的最小距离
                    for (int mEnd = 0;mEnd<mLen;mEnd++) {
                        int mmDis = mToOdis[m][o] + oToMdis[o][mEnd];
                        if (mmDis < mToMdis[m][mEnd]) {
                            mToMdis[m][mEnd] = mmDis;
                        }
                    }
                }
            }
            int mStatu = (1 << mLen);
            int[][][] dp = new int[mLen+1][mStatu][mLen];
            int bigStatu = mStatu - 1;
            for (int m = 0; m < mLen;m++) {
                dp[mLen][bigStatu][m] = mToTdis[m];    
            }
            // dp指该状态下该位置的m到终点的最短距离
            // 对于那个m他要选1个m
            for (int mCount = mLen-1;mCount>=1;mCount--) {
                for(int statu = bigStatu; statu>=0;statu--) {
                    for (int m = 0; m < mLen;m++) {
                        int minDis = MAX_VALUE;
                        for(int endM = 0;endM < mLen;endM++) {
                            // endM必须是0
                            if ((statu & (1<<endM)) != 0 || m == endM) {
                                continue;
                            }
                            int realDis = mToMdis[m][endM] + dp[mCount+1][statu | (1<<endM)][endM];
                            if (realDis < minDis) {
                                minDis = realDis;
                            }
                        }
                        dp[mCount][statu][m] = minDis;
                    }
                }
            }
            
            int result = MAX_VALUE;
            for (int m = 0; m < mLen;m++) {
                int sToMToTDis = sToMdis[m] + dp[1][1<<m][m];
                if (sToMToTDis < result) {
                   result = sToMToTDis; 
                }
            }
            return result == MAX_VALUE?-1:result;
        }
        
        Map<Integer, Integer> bfs(int x, int y, char findChar) {
            boolean[][] vis = new boolean[map.length][map[0].length];
            Queue<Point> queue = new LinkedList<>();
            queue.offer(new Point(x, y));
            vis[y][x] = true;
            Map<Integer, Integer> indexAndDisMap = new HashMap<>();
            while(!queue.isEmpty()) {
                Point p = queue.poll();
                int px = p.x;
                int py = p.y;
                if (map[py][px] == findChar) {
                    indexAndDisMap.put(pointToIndex.get(p), p.dis);
                }
                deal(px+1,py,queue,vis, p.dis + 1);
                deal(px-1,py,queue,vis, p.dis + 1);
                deal(px,py-1,queue,vis, p.dis + 1);
                deal(px,py+1,queue,vis, p.dis + 1);
            }
            return indexAndDisMap;
        }
    
        void deal(int x,int y, Queue<Point> queue, boolean[][] vis, int dis) {
            if (!vis[y][x] && map[y][x] != '#') {
                vis[y][x] = true;
                queue.offer(new Point(x,y).setDis(dis));
            }
        }
    }
    

    思路

    我是真的不会

    第一轮状态压缩

    先利用bfs
    求出起点S到所有O的最短路径s2oDis[] (2就是英文to的意思)
    求出所有O到所有M的最短路径o2mDis[][]
    求出所有M到所有O的最短路径m2oDis[][] (这个其实把上面的o2mDis反一下就行了,我写的时候忘记了,又重新跑一边)
    求出所有M到终点T的最短路径m2tDis[]
    这时候相当于已经对图做了一轮压缩了。
    但这时候还是不能起做搜索, O的节点有40个,M的节点有16个,合起来相当于最多有六十多层,铁定超时。

    第二轮压缩

    那么还能压缩吗?
    可以!
    S到每个M的时候,中间至少要经过1个O
    每个M到其他的M时,也至少要经过1个O
    那么S到每个M的距离s2mDis[mIndex] = Min(s2oDis[oIndex] + o2mDis[oIndex][mIndex])
    直接对oIndex和mIndex做2个for循环搞定。

       for (int m = 0; m < mLen;m++) {
                for (int o = 0; o < oLen;o++) {
                    // 计算s到各m且至少经过1个O的最小距离
                    int dis = sToOdis[o] + oToMdis[o][m];
                    if (dis < sToMdis[m]) {
                        sToMdis[m] = dis;
                    }
                }
            }
    
    

    M到各M同理,于是我们又得到了m2mDis[][]

    那么整个图就压缩成了3个数组,并且节点最多也就18个。
    起点S到个M的最短距离 s2mDis[]
    各M到各M的最短距离 m2mDis[][]
    各M到终点的最短距离 m2tDis[]

    搜索和剪枝

    那么来就是个搜索问题了,dfs或者bfs都可以 ,最多也就18层。
    但是这里有个条件: 必须经过所有的M。以至于你得注意剪枝。避免反复搜重复的局面。

    那么如何判断局面是否经历过?
    题目把M的最大个数正好设定成16,其实就是在暗示你用位运算来记录已走路线。
    假设M有4个,则二进制(1001)=9代表了此时第0个M和第3个M已被走过,可以用statu来表示
    还得注意一下此时所处M的位置, 用nowMIndex表示
    则我们就可以用数组minDis[statu][nowMIndex]来记录已搜索到的最短路径了,做一下剪枝,就能AC了。
    非搜索的dp方式
    除了用bfs和dfs做最后的搜索, 也可以用dp,我是用dp的,因为之前做过类似的题目。
    dp[已走的M个数][当前M局面]][当前所处M位置] 等于该状态下到T的最短距离

    dp[已走的M个数][当前M局面]][当前所处M位置]
    = min( dp[已走M个数+1][选择下一个M后的局面][下一个M的位置] )
    dp的时候从最后的局面一步步往前搜即可。

    注:第一个下标[已走M的个数]也可以不需要,避免局面值已经能计算出M的个数了
    但我为了方便写还是用了,代价就是最长例子用了1.4秒差点超了,如果会超时我就去掉这个下标

    
            int mStatu = (1 << mLen);
            int[][][] dp = new int[mLen+1][mStatu][mLen];
            int bigStatu = mStatu - 1;
            for (int m = 0; m < mLen;m++) {
                dp[mLen][bigStatu][m] = mToTdis[m];    
            }
            // dp指该状态下该位置的m到终点的最短距离
            //dp[已走的M个数][当前M局面]][当前所处M位置] 
            for (int mCount = mLen-1;mCount>=1;mCount--) {
                for(int statu = bigStatu; statu>=0;statu--) {
                    for (int m = 0; m < mLen;m++) {
                        int minDis = MAX_VALUE;
                        for(int endM = 0;endM < mLen;endM++) {
                            // endM所在位必须是0。
                            if ((statu & (1<<endM)) != 0 || m == endM) {
                                continue;
                            }
                            int realDis = mToMdis[m][endM] + dp[mCount+1][statu | (1<<endM)][endM];
                            if (realDis < minDis) {
                                minDis = realDis;
                            }
                        }
                        dp[mCount][statu][m] = minDis;
                    }
                }
            }
    
    

    需要注意的情况(如果用搜索可能会陷入这个坑)
    不存在M和O的情况
    只存在O,不存在M( 这个贼坑)
    S到不了T
    S到不了所有的M
    存在M到不了任意一个O

  • 相关阅读:
    4.父类私有属性和方法
    3.单继承和方法的重写
    2.Python封装练习及私有属性
    1.Python面向对象基础
    6.类与对象、封装、构造方法
    5.数组
    4.方法
    3.流程控制语句
    结构型模式和物理视图,模型管理试图的总结
    创建型模式和关于状态视图、活动视图、交互视图的总结
  • 原文地址:https://www.cnblogs.com/charlottepl/p/13395507.html
Copyright © 2011-2022 走看看