Skip to content

Latest commit

 

History

History
255 lines (216 loc) · 8.95 KB

20200729-13.xun-bao.md

File metadata and controls

255 lines (216 loc) · 8.95 KB

13.xun-bao

题目链接:https://leetcode-cn.com/problems/xun-bao/

参考链接:

https://leetcode-cn.com/problems/xun-bao/solution/xun-bao-bfs-dp-by-leetcode-solution/

https://leetcode-cn.com/problems/xun-bao/solution/wu-xian-bao-li-mei-you-ren-he-ji-qiao-jiu-shi-luo-/

题目

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

迷宫是一个二维矩阵,用一个字符串数组表示。它标识了唯一的入口(用 '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 。

解题

思路[1]多步骤暴力

  • 看代码
  • 思路比较简单,求出各项到各项的最短距离然后加起来,要考虑到边界情况和两个机关之间要取石头的情况

代码

/**
 * @param {string[]} maze
 * @return {number}
 * 求从起点到所有石头的最短步数,
 * 求各个机关到终点的最短步数
 * 求各个机关到各个石头的最短步数
 * 进而求出机关到机关的最短步数
 * 最后求出从起点出发到激活所有机关的最短步数
 * 最后求从起点出发激活所有机关 + 从最后一个机关到终点的最短步数
 * 一堆石头有无限个石头, 一个石头只能激活一个机关
 */
// 二位数组记录从某个点出发到其他点的最小步数
let dist = null
// 求dist的时候记录某个点是否够已经访问过
let visited = null
// 记录各个机关到各堆巨石的最小步数
let distM = null
// 记录从起点出发到各个石块的最小步数
let distS = null
// 机关经石头到各个机关的最小步数
let distBM = null
// 各个机关到终点的最小步数
let distT = null


// 记录各个机关到终点的最小步数
const dirs = [[-1, 0], [0, 1], [1, 0], [0, -1]]

// 所有机关
let puzzles = null
// 所有石堆位置
let stones = null

var minimalSteps = function(maze) {
    let stoneNum = 0
    let puzzleNum = 0
    let start = null
    let end = null
    const m = maze.length
    const n = maze[0].length
    stones = []
    puzzles = []
    distS = generateArray(m, n, -1)
    distT = generateArray(m, n, -1)
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            // 石头
            if(maze[i][j] === 'O') {
                stones.push([i, j])
                stoneNum++
                // 机关
            } else if(maze[i][j] === 'M'){
                puzzles.push([i, j])
                puzzleNum++
                // 起点
            } else if(maze[i][j] === 'S') {
                start = [i, j]
                // 终点
            } else if(maze[i][j] === 'T') {
                end = [i, j]
            }
        }
    }
    distM= generateArray(puzzleNum, stoneNum, -1)
    distBM = generateArray(puzzleNum, puzzleNum, -1)
    bfs(maze, start, m, n)
    if(puzzleNum === 0) return dist[end[0]][end[1]]

    // 求起点到各个石堆的最小步数
    for(let stone of stones) distS[stone[0]][stone[1]] = dist[stone[0]][stone[1]]
    bfs(maze, end, m, n)
    // 求终点到各个机关的最小步数
    for(let p of puzzles) distT[p[0]][p[1]] = dist[p[0]][p[1]]
    // 求各个机关到各个石堆的最小步数
    for(let i = 0; i < puzzleNum; i++) {
        bfs(maze, puzzles[i], m, n)
        for(let j = 0; j < stoneNum; j++) {
            distM[i][j] = dist[stones[j][0]][stones[j][1]]
        }
    }
    /**
     * 接下来求从起点出发开启所有机关, 最后一个机关是某个的最小步数,分两步,
     * 第一步求得从起点出发只开启一个机关的最小步数
     * 第二步根据第一步的结果生成从起点出发开启所有机关、最后一个开启的机关是某一个的最小步数
     */

    let all = 1 << puzzleNum
    // dp 是二维数组, 第一维二进制表示某一位为1表示这一机关是开启状态, 第二位只是最后开启的是哪个机关
    let dp = generateArray(all, 16, -1)
    for(let i = 0; i < puzzleNum; i++) {
        let idx = 1 << i
        let best = -1
        for(let j = 0; j < stoneNum; j++) {
            if((distS[stones[j][0]][stones[j][1]] === -1) || (distM[i][j] === -1)) continue
            let value = distS[stones[j][0]][stones[j][1]] + distM[i][j]
            if((best === -1) || value < best) best = value
        }
        dp[idx][i] = best
    }

    // 求出机关到机关之间经过巨石的最短步数, 因为一块石头只能开启一个机关
    for(let i = 0; i < puzzleNum; i++) {
        for(let j = 0; j < puzzleNum; j++) {
            if(i !== j) {
                let best = -1
                for(let k = 0; k < stoneNum; k++) {
                    if((distM[i][k] !== -1) && (distM[j][k] !== -1)) {
                        let value = distM[i][k] + distM[j][k]
                        if((best === -1) || (value < best)) best = value
                    }
                }
                distBM[i][j] = best
            } else distBM[i][j] = 0
        }
    }

    // 求出从起点出发激活所有机关、最后一个机关被激活的最小步数, 求得是dp[i][j]
    for(let i = 1; i < all; i++) {
        for(let j = 0; j < puzzleNum; j++) {
            // 每一轮只求j
            if(i & (1 << j)) {
                for(let k = 0; k < puzzleNum; k++) {
                    // j != k && (i & (1 << k)) && dp[i - (1 << j)][k] != -1 && distBM[k][j] != -1
                    // j 不等于k, k已被开启、j未被开启的情况存在, k、j之间有路径可达
                    if((j !== k) && (i & (1 << k)) && (dp[i - (1 << j)][k] !== -1) && (distBM[k][j] !== -1)) {
                        let value = dp[i - (1 << j)][k] + distBM[k][j]
                         // 谁小我就从谁过来
                        if((dp[i][j] === -1) || (value < dp[i][j])) dp[i][j] = value
                    }
                }
            }
        }
    }

    /**
     * 最后求开启所有机关最小步数 + 从一个机关到终点的最小步数的和的最小值,这个就是答案
     *
     */
    let ans = Infinity
    let left = all - 1 // that is 1111111111111111
    for(let i = 0; i < puzzleNum; i++) {
        if((dp[left][i] !== -1) && (distT[puzzles[i][0]][puzzles[i][1]] !== -1)) {
            let value = dp[left][i] + distT[puzzles[i][0]][puzzles[i][1]]
            if(value < ans) ans = value
        }
    }
    return ans === Infinity ? -1 : ans
};


function bfs(maze, entry, m, n) {

    dist = generateArray(m, n, -1)
    visited = generateArray(m, n , 0)

    let queue = [entry]
    visited[entry[0]][entry[1]] = 1
    dist[entry[0]][entry[1]] = 0
    let step = 0
    while(queue.length) {
        step++
        let len = queue.length
        for (let i = 0; i < len; i++) {
            let [x, y] = queue.pop()
            for (let dir of dirs) {
                let newX = x + dir[0]
                let newY = y + dir[1]
                if ((newX < 0) || (newX >= m) || (newY < 0) || (newY >= n) || visited[newX][newY] || (maze[newX][newY] === '#')) {
                    continue
                }
                dist[newX][newY] = step
                visited[newX][newY] = 1
                queue.unshift([newX, newY])
            }
        }
    }
}

function generateArray(m, n, fill){
    let arr = new Array(m)
    for(let i = 0; i < m; i++) arr[i] = new Array(n).fill(fill)
    return arr
}

思考