🌞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

原文地址:https://www.cnblogs.com/charlottepl/p/13395507.html