LeetCode LCP 13. 寻宝(状压dp)

LCP 13. 寻宝

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

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

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

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

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

示例 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。
在这里插入图片描述

示例 2:

输入: [“S#O”, “M.#”, “M.T”]

输出:-1

解释:我们无法搬到石头触发机关

示例 3:

输入: [“S#O”, “M.T”, “M…”]

输出:17

解释:注意终点也是可以通行的。

限制:

1 <= maze.length <= 100
1 <= maze[i].length <= 100
maze[i].length == maze[j].length
S 和 T 有且只有一个
0 <= M的数量 <= 16
0 <= O的数量 <= 40,题目保证当迷宫中存在 M 时,一定存在至少一个 O 。

思路

bfs + 状压dp.
最短路线一定是S-O-M1-O-M2-...-O-Mm-T,可以分解成S-O-M, Mi-O-Mj, M-T三种类型的子路径,每种子路径都是原来图上的路径的组合,且子路径最小值是确定的,可以通过bfs + 遍历所有可能的O取极小得到.
于是问题转化即求从S出发,以某种顺序经过所有的M,最终到达T的最短路径,其中两节点间的最短子路径已知且相互独立。此即为旅行商问题,用状态压缩动态规划求解。s的每一个二进制位i=0/1表示是否经过了Midp[s][i]表示经过s状态的节点且当前位于节点i的最短路径。

代码

class Solution {
    private static final int[] moveX = {-1, 1, 0, 0}, moveY = {0, 0, -1, 1};

    /**
    * Calculate distances between all position pairs.
    * Position (x, y) is represented by (x * n + y) in a (m, n) maze.
    */
    private int[] bfs(String[] maze, int start, int m, int n) {
        int[] ret = new int[m * n];
        Arrays.fill(ret, -1);
        ret[start] = 0;
        LinkedList<Integer> queue = new LinkedList<>();
        queue.add(start);
        while (!queue.isEmpty()) {
            int head = queue.removeFirst(), headX = head / n, headY = head % n;
            for (int move=0; move<4; ++move) {
                int tx = headX + moveX[move], ty = headY + moveY[move];
                if (tx >=0 && tx < m && ty >= 0 && ty < n) {
                    int target = tx * n + ty;
                    if (ret[target] == -1 && maze[tx].charAt(ty) != '#') {
                        queue.add(target);
                        ret[target] = ret[head] + 1;
                    }
                }
            }
        }
        return ret;
    }

    public int minimalSteps(String[] maze) {
        int m = maze.length, n = maze[0].length(), start = 0, end = 0, i = 0, j = 0;
        ArrayList<Integer> mArr = new ArrayList<>(), oArr = new ArrayList<>();
        for (i=0; i<m; ++i) {
            for (j=0; j<n; ++j) {
                char ch = maze[i].charAt(j);
                int pos = i * n + j;
                if (ch == 'S') {
                    start = pos;
                } else if (ch == 'T') {
                    end = pos;
                } else if (ch == 'M') {
                    mArr.add(pos);
                } else if (ch == 'O') {
                    oArr.add(pos);
                }
            }
        }
        int[] startDis = bfs(maze, start, m, n);
        int numM = mArr.size(), numO = oArr.size();
        if (numM == 0) {
            return startDis[end];
        }
        int[] endDis = bfs(maze, end, m, n);
        int[] emDis = new int[numM], smDis = new int[numM];
        for (i=0; i<numM; ++i) {
            emDis[i] = endDis[mArr.get(i)] > 0? endDis[mArr.get(i)]: Integer.MAX_VALUE;
        }
        int[][] mAllDis = new int[numM][m * n];
        for (i=0; i<numM; ++i) {
            mAllDis[i] = bfs(maze, mArr.get(i), m, n);
        }
        for (i=0; i<numM; ++i) {
            smDis[i] = Integer.MAX_VALUE;
            for (int o: oArr) {
                if (startDis[o] > 0 && mAllDis[i][o] > 0) {
                    smDis[i] = Math.min(startDis[o] + mAllDis[i][o], smDis[i]);
                }
            }
        }
        int[][] mmDis = new int[numM][numM];
        for (i=0; i<numM; ++i) {
            for (j=0; j<numM; ++j) {
                mmDis[i][j] = Integer.MAX_VALUE;
                for (int o: oArr) {
                    if (mAllDis[i][o] > 0 && mAllDis[j][o] > 0) {
                        mmDis[i][j] = Math.min(mAllDis[i][o] + mAllDis[j][o], mmDis[i][j]);
                    }
                }
            }
        }
        int numState = 1 << numM;
        int[][] dp = new int[numState][numM];
        for (i=0; i<numM; ++i) {
            dp[1<<i][i] = smDis[i];
        }
        for (int s = 1; s < numState; ++s) {
            for (i=0; i<numM; ++i) {
                int ss = s - (1 << i);
                if ((s & (1 << i)) != 0 && s != (1 << i)) {
                    dp[s][i] = Integer.MAX_VALUE;
                    for (j=0; j<numM; ++j) {
                        if (i != j && (s & (1 << j)) != 0 && mmDis[i][j] < Integer.MAX_VALUE && dp[ss][j] < Integer.MAX_VALUE) {
                            dp[s][i] = Math.min(dp[ss][j] + mmDis[i][j], dp[s][i]);
                        }
                    }
                }
            }
        }
        int ret = Integer.MAX_VALUE;
        for (i=0; i<numM; ++i) {
            if (dp[numState-1][i] < Integer.MAX_VALUE && emDis[i] < Integer.MAX_VALUE) {
                ret = Math.min(dp[numState-1][i] + emDis[i], ret);
            }
        }
        return ret < Integer.MAX_VALUE? ret: -1;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值