灵神力扣题单之网格图(中)

21 阅读47分钟

1 介绍

本博客用来记录灵神力扣题单之网格图

2 训练

2.3 BFS(求最短路)

题目29909. 蛇梯棋

解题思路:注意审题。st数组只更新下一个结点的值,经过的梯子入口不算。

C++代码如下,

class Solution {
public:
    int snakesAndLadders(vector<vector<int>>& board) {
        int n = board.size();
        vector<bool> st(n*n+1, false);
        vector<vector<int>> grid(n, vector<int>(n, -1));
        int flag = 1;
        int idx = 1;
        for (int i = n-1; i >= 0; --i) {
            if (flag > 0) {
                for (int j = 0; j < n; ++j) {
                    grid[i][j] = idx++;
                } 
            } else {
                for (int j = n-1; j >= 0; --j) {
                    grid[i][j] = idx++;
                }
            }
            flag ^= 1;
        }

        unordered_map<int,int> map_x_y;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (board[i][j] != -1) {
                    map_x_y[grid[i][j]] = board[i][j];
                }
            }
        }
 
        //计算答案
        int level = 0;
        queue<int> q;
        q.push(1);
        st[1] = true;
        while (!q.empty()) {
            int qsize = q.size();
            while (qsize--) {
                int i = q.front();
                if (i == n*n) { //返回答案
                    return level;
                }
                q.pop();
                for (int k = 1; k < 7; ++k) {
                    int ni = i + k;
                    if (ni > n*n) continue;
                    if (map_x_y.find(ni) != map_x_y.end()) {
                        ni = map_x_y[ni];
                    }
                    if (st[ni]) continue;
                    q.push(ni);
                    st[ni] = true;
                }
            }
            level += 1;
        }
        return -1;
    }
};

python3代码如下,

class Solution:
    def snakesAndLadders(self, board: List[List[int]]) -> int:
        n = len(board)
        grid = [[-1] * n for _ in range(n)]
        idx = 1
        flag = True 
        for i in range(n-1,-1,-1): 
            if flag:
                for j in range(n):
                    grid[i][j] = idx
                    idx += 1
            else:
                for j in range(n-1,-1,-1):
                    grid[i][j] = idx
                    idx += 1
            flag = (flag == False) #更新flag
        
        map_val1_val2 = collections.defaultdict(int)
        for i in range(n):
            for j in range(n):
                if board[i][j] != -1:
                    map_val1_val2[grid[i][j]] = board[i][j]
        
        st = [False] * (n * n + 1)
        q = collections.deque([])
        q.append(1)
        st[1] = True 
        level = 0
        while len(q) > 0:
            tot = len(q)
            while tot > 0:
                tot -= 1
                i = q.popleft()

                if i == n*n: #提前判断
                    return level 

                for k in range(1,7):
                    ni = i + k 
                    if ni > n * n:
                        continue
                    if ni in map_val1_val2:
                        ni = map_val1_val2[ni] #更新下一个结点
                    #下一个结点只能有一个
                    if st[ni]:
                        continue 
                    q.append(ni)
                    st[ni] = True 

            level += 1
        return -1

题目301210. 穿过迷宫的最少移动次数

解题思路:状态多一维,表示水平方向或者竖直方向。bfs。

C++代码如下,

class Solution {
public:
    int minimumMoves(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        vector<vector<vector<bool>>> st(n, vector<vector<bool>>(m, vector<bool>(2, false)));
        int dirs[3][3] = {{1,0,0},{0,1,0},{0,0,1}};

        //特判
        if (grid[0][0] == 1 || grid[0][1] == 1 || grid[n-1][n-2] == 1 || grid[n-1][n-1] == 1) {
            return -1;
        }

        //蛇尾(i,j),s=0表示水平方向,s=1表示竖直方向
        //则蛇头(i+s,j+1-s)

        queue<vector<int>> q;
        q.push({0,0,0});
        st[0][0][0] = true;

        int level = 0;
        while (!q.empty()) {
            int qsize = q.size();
            while (qsize--) {
                auto t = q.front();
                q.pop();
                int i = t[0];
                int j = t[1];
                int s = t[2];
                if (i == n-1 && j == n-2 && s == 0) { //返回答案
                    return level;
                }
                for (int k = 0; k < 3; ++k) {
                    int ni = i + dirs[k][0];
                    int nj = j + dirs[k][1];
                    int ns = s ^ dirs[k][2];
                    if (ni+ns < 0 || ni+ns >= n || nj+1-ns < 0 || nj+1-ns >= m) continue;
                    if (st[ni][nj][ns]) continue;
                    if (grid[ni][nj] == 1 || grid[ni+ns][nj+1-ns] == 1) continue;
                    if (k == 2 && grid[i+1][j+1] == 1) continue; //操作编号为2时,需要额外判断
                    q.push({ni,nj,ns});
                    st[ni][nj][ns] = true;
                }
            }
            level += 1;
        }
        return -1;
    }
};

python3代码如下,

class Solution:
    def minimumMoves(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])
        st = [[[False] * 2 for _ in range(m)] for _ in range(n)]
        dirs = [[1,0,0], [0,1,0], [0,0,1]] 
        #(i,j,s)其中s=0表示水平方向,s=1表示竖直方向
        #蛇尾(i,j),则蛇头(i+s,j+1-s)

        #特判
        if grid[0][0] == 1 or grid[0][1] == 1 or grid[n-1][n-2] == 1 or grid[n-1][n-1] == 1:
            return -1 

        q = collections.deque([])
        q.append((0,0,0)) 
        st[0][0][0] = True
        level = 0  
        while len(q) > 0:
            qsize = len(q)
            while qsize > 0:
                qsize -= 1
                i,j,s = q.popleft()
                if i == n-1 and j == n-2 and s == 0: #返回答案
                    return level 
                for k in range(3):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    ns = s ^ dirs[k][2]
                    if ni+ns < 0 or ni+ns >= n or nj+1-ns < 0 or nj+1-ns >= m:
                        continue 
                    if st[ni][nj][ns]:
                        continue 
                    if grid[ni][nj] == 1 or grid[ni+ns][nj+1-ns] == 1:
                        continue 
                    if k == 2 and grid[i+1][j+1] == 1: #操作编号k=2时,需要额外判断
                        continue 
                    q.append([ni,nj,ns])
                    st[ni][nj][ns] = True 
            level += 1
        return -1

题目31675. 为高尔夫比赛砍树

解题思路:每两个点之间做一次bfs。

C++代码如下,

class Solution {
public:
    int cutOffTree(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();

        if (grid[0][0] == 0) { //特判
            return -1;
        }

        vector<int> nums;
        unordered_map<int,pair<int,int>> map_num_xy;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] > 1) {
                    nums.push_back(grid[i][j]);
                    map_num_xy[grid[i][j]] = make_pair(i,j);
                }
            }
        }
        sort(nums.begin(), nums.end());

        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};

        function<int(pair<int,int>,pair<int,int>)> compute =[&] (pair<int,int> snode, pair<int,int> enode) -> int {
            vector<vector<bool>> st(n, vector<bool>(m, false));
            queue<pair<int,int>> q;
            q.push(snode);
            st[snode.first][snode.second] = true;
            int level = 0;
            while (!q.empty()) {
                int qsize = q.size();
                while (qsize--) {
                    auto [i,j] = q.front();
                    q.pop();

                    if (i == enode.first && j == enode.second) { //返回答案
                        return level;
                    }

                    for (int k = 0; k < 4; ++k) {
                        int ni = i + dirs[k][0];
                        int nj = j + dirs[k][1];
                        if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                        if (st[ni][nj]) continue;
                        if (grid[ni][nj] == 0) continue;
                        q.push(make_pair(ni,nj));
                        st[ni][nj] = true;
                    }
                }
                level += 1;
            }
            return -1;
        };

        pair<int,int> snode(0,0);
        int res = 0;
        for (int num : nums) {
            pair<int,int> enode = map_num_xy[num];
            int ans = compute(snode,enode);
            if (ans == -1) {
                return -1;
            }
            res += ans;
            snode = enode; //更新snode
        }
        return res;
    }
};

python3代码如下,

class Solution:
    def cutOffTree(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])

        if grid[0][0] == 0: #特判
            return -1

        nums = []
        map_val_xy = collections.defaultdict(list)
        for i in range(n):
            for j in range(m):
                if grid[i][j] > 1:
                    nums.append(grid[i][j])
                    map_val_xy[grid[i][j]] = [i,j]
        nums.sort()

        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        def compute(snode: list, enode: list) -> int:
            st = [[False] * m for _ in range(n)]
            q = collections.deque([])
            q.append(snode)
            st[snode[0]][snode[1]] = True 
            level = 0
            while len(q) > 0:
                qsize = len(q)
                while qsize > 0:
                    qsize -= 1
                    i, j = q.popleft()
                    if i == enode[0] and j == enode[1]: #返回答案
                        return level 
                    for k in range(4):
                        ni = i + dirs[k][0]
                        nj = j + dirs[k][1]
                        if ni < 0 or ni >= n or nj < 0 or nj >= m:
                            continue 
                        if st[ni][nj]:
                            continue 
                        if grid[ni][nj] == 0:
                            continue 
                        q.append([ni,nj])
                        st[ni][nj] = True 
                level += 1
            return -1


        res = 0
        snode = [0,0]
        for i in range(len(nums)):
            enode = map_val_xy[nums[i]]
            ans = compute(snode,enode)
            if ans == -1:
                return -1
            res += ans 
            snode = enode #更新snode
        return res 

题目32749. 隔离病毒

解题思路:考虑一层一层的元素。

C++代码如下,

class Solution {
private:
    vector<vector<int>> grid;
    int n;
    int m;
    int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
public:
    vector<vector<pair<int,int>>> get_vec_coords() {
        vector<vector<pair<int,int>>> res;

        vector<vector<bool>> st(n, vector<bool>(m, false));
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == 1 && !st[i][j]) {
                    vector<pair<int,int>> coords;
                    queue<pair<int,int>> q;
                    q.push(make_pair(i,j));
                    st[i][j] = true;
                    coords.push_back(make_pair(i,j));
                    while (!q.empty()) {
                        auto [x,y] = q.front();
                        q.pop();
                        for (int k = 0; k < 4; ++k) {
                            int nx = x + dirs[k][0];
                            int ny = y + dirs[k][1];
                            if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                            if (st[nx][ny]) continue;
                            if (grid[nx][ny] != 1) continue;
                            q.push(make_pair(nx,ny));
                            st[nx][ny] = true;
                            coords.push_back(make_pair(nx,ny));
                        }
                    }
                    res.push_back(coords);
                }
            }
        }

        return res;
    }

    pair<int,int> compute(vector<pair<int,int>> coords) {
        int harm = 0;
        int door = 0;
        vector<vector<bool>> st(n, vector<bool>(m, false));
        for (auto [i,j] : coords) {
            st[i][j] = true;
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                if (grid[ni][nj] != 0) continue;
                door += 1;
                if (st[ni][nj]) continue;
                harm += 1;
                st[ni][nj] = true;
            }
        }
        return make_pair(harm, door);
    }

    void update(vector<pair<int,int>>& coords) {
        //更新grid
        vector<vector<bool>> st(n, vector<bool>(m, false));
        for (auto [i,j] : coords) {
            st[i][j] = true;
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                if (grid[ni][nj] != 0) continue;
                if (st[ni][nj]) continue;
                st[ni][nj] = true;
                grid[ni][nj] = 1; //更新grid
            }
        }
        return;
    }

    int containVirus(vector<vector<int>>& grid) {
        this->grid = grid;
        this->n = grid.size();
        this->m = grid[0].size();

        vector<vector<pair<int,int>>> vec_coords = get_vec_coords(); //从grid中获取vec_coords

        //cout << "vec_coords.size() = " << vec_coords.size() << endl;

        int res = 0;
        while (vec_coords.size() > 0) {
            int target_i = 0;
            auto [target_harm, target_door] = compute(vec_coords[0]);
            for (int i = 1; i < vec_coords.size(); ++i) {
                auto [curr_harm, curr_door] = compute(vec_coords[i]);
                if (curr_harm > target_harm) {
                    target_i = i;
                    target_harm = curr_harm;
                    target_door = curr_door;
                }
            }

            if (target_harm == 0) { //提前返回
                return res;
            }

            //找到了需要处理的病毒集团,它的下标为target_i
            res += target_door;
            for (auto [i,j] : vec_coords[target_i]) {
                this->grid[i][j] = -1; //更新this->grid
            }
            for (int i = 0; i < vec_coords.size(); ++i) {
                if (i == target_i) continue;
                update(vec_coords[i]); //更新grid
            }

            vec_coords = get_vec_coords(); //更新vec_coords
        }
        return res;
    }
};

python3代码如下,

class Solution:
    def containVirus(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        
        
        def bfs(i: int, j: int, st: list) -> list:
            res = []
            q = collections.deque([])
            q.append([i,j])
            st[i][j] = True 
            res.append([i,j])
            while len(q) > 0:
                i, j = q.popleft()
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if st[ni][nj]:
                        continue 
                    if grid[ni][nj] != 1:
                        continue 
                    q.append([ni,nj])
                    st[ni][nj] = True 
                    res.append([ni,nj])
            return res 
        
        def get_vec_coords() -> list:
            vec_coords = []
            st = [[False] * m for _ in range(n)]
            for i in range(n):
                for j in range(m):
                    if grid[i][j] == 1 and not st[i][j]:
                        coords = bfs(i,j, st)
                        vec_coords.append(coords)
            return vec_coords

        def compute(coords: list) -> int:
            st = [[False] * m for _ in range(n)]
            harm = 0
            door = 0
            for i,j in coords:
                st[i][j] = True 
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if grid[ni][nj] != 0:
                        continue 
                    door += 1
                    if st[ni][nj]:
                        continue 
                    st[ni][nj] = True 
                    harm += 1
            return harm,door
                    
        def update(coords: list) -> None:
            st = [[False] * m for _ in range(n)]
            for i,j in coords:
                st[i][j] = True 
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if grid[ni][nj] != 0:
                        continue 
                    if st[ni][nj]:
                        continue 
                    st[ni][nj] = True 
                    grid[ni][nj] = 1 #把grid中的相应位置进行更新
            return
        
        #计算答案
        res = 0
        vec_coords = get_vec_coords()
        while len(vec_coords) > 0:
            #且该感染区域对未感染区域的威胁最大且 保证唯一 
            target_i = 0
            target_harm, target_door = compute(vec_coords[0])
            for i in range(1,len(vec_coords)):
                curr_harm, curr_door = compute(vec_coords[i])
                if curr_harm > target_harm:
                    target_harm = curr_harm 
                    target_i = i 
                    target_door = curr_door 

            if target_harm == 0: #提前返回
                return res 
            
            #target_i被控制
            res += target_door 

            #更新vec_coords
            for i,j in vec_coords[target_i]:
                grid[i][j] = -1 #grid[i][j]=-1表示原先是1,但现在用防火墙控制住了,设置为-1
            for i in range(len(vec_coords)):
                if i == target_i:
                    continue 
                update(vec_coords[i])
            vec_coords = get_vec_coords() #根据grid获取vec_coords

        return res 

2.4 01BFS(边权只有0和1)

题目331368. 使网格图至少有一条有效路径的最小代价

解题思路:边权只有0和1的图,可以考虑使用01bfs算法,它的时间复杂度是O(N)O(N)的,比dijkstra算法要快。遇到边权为0的,在队头插入;遇到边权为1的,在队尾插入。

C++代码如下,

class Solution {
public:
    int minCost(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int dirs[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
        vector<vector<int>> d(n, vector<int>(m, n+m));
        vector<vector<bool>> st(n, vector<bool>(m, false));

        deque<pair<int,int>> dq;
        dq.push_back(make_pair(0,0));
        d[0][0] = 0;
        
        while (!dq.empty()) {
            auto [i,j] = dq.front();
            dq.pop_front();
            if (st[i][j]) continue;
            st[i][j] = true;
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                int t = grid[i][j] == k+1 ? 0 : 1;
                if (d[ni][nj] > d[i][j] + t) {//求最小值
                    d[ni][nj] = d[i][j] + t;
                    if (t == 0) {
                        dq.push_front(make_pair(ni,nj)); //边权为0,往队头插入
                    } else {
                        dq.push_back(make_pair(ni,nj)); //边权为1,往队尾插入
                    }
                }
            }
        }
        return d[n-1][m-1];
    }
};

python3代码如下,

class Solution:
    def minCost(self, grid: List[List[int]]) -> int:
        #边权只有0或1可以使用0-1bfs
        #遇到边权为0的,在队头插入;遇到边权为1的,在队尾插入
        n, m = len(grid), len(grid[0])
        dirs = [[0,1],[0,-1],[1,0],[-1,0]] #k=0,往右走;k=1,往左走。。。
        
        d = [[inf] * m for _ in range(n)]
        st = [[False] * m for _ in range(n)]
        q = collections.deque([])
        q.append([0,0])      
        d[0][0] = 0 
        while len(q) > 0:
            i,j = q.popleft()
            if st[i][j]:
                continue 
            st[i][j] = True 
            for k in range(4):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                if d[ni][nj] > d[i][j] + int(grid[i][j] != k+1):
                    d[ni][nj] = d[i][j] + int(grid[i][j] != k+1)
                    if grid[i][j] != k+1:
                        q.append([ni,nj])
                    else:
                        q.appendleft([ni,nj])
        return d[n-1][m-1] 
            

题目342290. 到达角落需要移除障碍物的最小数目

解题思路:01bfs。

C++代码如下,

class Solution {
public:
    int minimumObstacles(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
        vector<vector<int>> d(n, vector<int>(m, n+m));
        vector<vector<bool>> st(n, vector<bool>(m, false));
        deque<pair<int,int>> dq;
        dq.push_back(make_pair(0,0));
        d[0][0] = 0;
        while (!dq.empty()) {
            auto [i,j] = dq.front();
            dq.pop_front();
            if (st[i][j]) continue;
            st[i][j] = true;
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                int edge = grid[ni][nj];
                if (d[ni][nj] > d[i][j] + edge) {
                    d[ni][nj] = d[i][j] + edge;
                    if (edge == 1) dq.push_back(make_pair(ni,nj));
                    else dq.push_front(make_pair(ni,nj));
                }
            } 
        }
        return d[n-1][m-1];
    }
};

python3代码如下,

class Solution:
    def minimumObstacles(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        st = [[False] * m for _ in range(n)]
        d = [[inf] * m for _ in range(n)]
        q = collections.deque([])
        q.append([0,0])
        d[0][0] = 0 
        while len(q) > 0:
            i,j = q.popleft()
            if st[i][j]:
                continue 
            st[i][j] = True 
            for k in range(4):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                edge = grid[ni][nj]
                if d[ni][nj] > d[i][j] + edge:
                    d[ni][nj] = d[i][j] + edge 
                    if edge == 1:
                        q.append([ni,nj])
                    elif edge == 0:
                        q.appendleft([ni,nj])
        return d[n-1][m-1]


题目353286. 穿越网格图的安全路径

解题思路:01bfs。

C++代码如下,

class Solution {
public:
    bool findSafeWalk(vector<vector<int>>& grid, int health) {
        int n = grid.size();
        int m = grid[0].size();
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
        vector<vector<int>> d(n, vector<int>(m, n+m));
        vector<vector<bool>> st(n, vector<bool>(m, false));
        deque<pair<int,int>> dq;
        dq.push_back(make_pair(0,0));
        d[0][0] = grid[0][0];
        while (!dq.empty()) {
            auto [i,j] = dq.front();
            dq.pop_front();
            if (st[i][j]) continue;
            st[i][j] = true;
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                int edge = grid[ni][nj];
                if (d[ni][nj] > d[i][j] + edge) {
                    d[ni][nj] = d[i][j] + edge;
                    if (edge == 1) dq.push_back(make_pair(ni,nj));
                    else dq.push_front(make_pair(ni,nj));
                }
            }
        }
        return health > d[n-1][m-1];
    }
};

python3代码如下,

class Solution:
    def findSafeWalk(self, grid: List[List[int]], health: int) -> bool:
        n, m = len(grid), len(grid[0])
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        d = [[inf] * m for _ in range(n)]
        st = [[False] * m for _ in range(n)]
        dq = collections.deque([])
        dq.append([0,0])
        d[0][0] = grid[0][0]
        while len(dq) > 0:
            i,j = dq.popleft()
            if st[i][j]:
                continue 
            st[i][j] = True 
            for k in range(4):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                edge = grid[ni][nj]
                if d[ni][nj] > d[i][j] + edge:
                    d[ni][nj] = d[i][j] + edge 
                    if edge == 1:
                        dq.append([ni,nj])
                    else:
                        dq.appendleft([ni,nj])
        return health > d[n-1][m-1]
                    

题目361824. 最少侧跳次数

解题思路:01bfs。或者动态规划。

C++代码如下,

class Solution {
public:
    int minSideJumps(vector<int>& obstacles) {
        int n = obstacles.size();
        int m = 3;
        int dirs[5][2] = {{1,0},{0,-1},{0,-2},{0,1},{0,2}};
        vector<vector<int>> d(n, vector<int>(m, n+m));
        vector<vector<bool>> st(n, vector<bool>(m, false));
        deque<pair<int,int>> dq;
        dq.push_back(make_pair(0,1));
        d[0][1] = 0;
        while (!dq.empty()) {
            auto [i,j] = dq.front();
            dq.pop_front();
            if (st[i][j]) continue;
            st[i][j] = true;
            for (int k = 0; k < 5; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                if (obstacles[ni]-1 == nj) continue;
                int edge = k == 0 ? 0 : 1;
                if (d[ni][nj] > d[i][j] + edge) {
                    d[ni][nj] = d[i][j] + edge;
                    if (edge == 1) {
                        dq.push_back(make_pair(ni,nj));
                    } else {
                        dq.push_front(make_pair(ni,nj));
                    }
                }
            } 
        } 
        int res = min(d[n-1][0],d[n-1][1]);
        res = min(res, d[n-1][2]);
        return res;
    }
};

python3代码如下,

class Solution:
    def minSideJumps(self, obstacles: List[int]) -> int:
        #动态规划可以做这道题
        #01bfs也可以做这道题
        n = len(obstacles)
        m = 3
        dirs = [[1,0],[0,-1],[0,-2],[0,1],[0,2]]
        d = [[inf] * m for _ in range(n)]
        st = [[False] * m for _ in range(n)]
        q = collections.deque([])
        q.append([0,1])
        d[0][1] = 0
        while len(q) > 0:
            i, j = q.popleft()
            if st[i][j]:
                continue 
            st[i][j] = True 
            for k in range(5):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                if obstacles[ni]-1 == nj:
                    continue 
                edge = 0 if k == 0 else 1
                if d[ni][nj] > d[i][j] + edge:
                    d[ni][nj] = d[i][j] + edge 
                    if edge == 1:
                        q.append([ni,nj])
                    else:
                        q.appendleft([ni,nj])
        res = min(d[n-1][0], d[n-1][1], d[n-1][2])
        return res 

题目37LCP 56. 信物传送

解题思路:01bfs。

C++代码如下,

class Solution {
public:
    int conveyorBelt(vector<string>& grid, vector<int>& start, vector<int>& end) {
        int n = grid.size();
        int m = grid[0].size();
        vector<vector<int>> d(n, vector<int>(m, n+m));
        vector<vector<bool>> st(n, vector<bool>(m, false));
        deque<pair<int,int>> dq;
        dq.push_back(make_pair(start[0],start[1]));
        d[start[0]][start[1]] = 0;

        int dirs[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
        unordered_map<char,int> map_c_k = {{'^',0},{'v',1},{'<',2},{'>',3}};

        while (!dq.empty()) {
            auto [i,j] = dq.front();
            dq.pop_front();
            if (st[i][j]) continue;
            st[i][j] = true;
            int target_k = map_c_k[grid[i][j]];
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                int edge = k == target_k ? 0 : 1;
                if (d[ni][nj] > d[i][j] + edge) {
                    d[ni][nj] = d[i][j] + edge;
                    if (edge == 1) {
                        dq.push_back(make_pair(ni,nj));
                    } else {
                        dq.push_front(make_pair(ni,nj));
                    }
                }
            }
        }
        return d[end[0]][end[1]];
    }
};

python3代码如下,

class Solution:
    def conveyorBelt(self, grid: List[str], start: List[int], end: List[int]) -> int:
        n, m = len(grid), len(grid[0])
        d = [[n+m] * m for _ in range(n)]
        st = [[False] * m for _ in range(n)]
        q = collections.deque([])
        q.append(start)
        d[start[0]][start[1]] = 0
        
        dirs = [[-1,0],[1,0],[0,-1],[0,1]]
        map_c_k = {'^':0, 'v':1, '<':2, '>':3}

        while len(q) > 0:
            i, j = q.popleft()
            if st[i][j]: 
                continue 
            st[i][j] = True 
            target_k = map_c_k[grid[i][j]]
            for k in range(4):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                edge = 0 if k == target_k else 1
                if d[ni][nj] > d[i][j] + edge:
                    d[ni][nj] = d[i][j] + edge 
                    if edge == 1:
                        q.append([ni,nj])
                    else:
                        q.appendleft([ni,nj])
        return d[end[0]][end[1]]

2.5 综合应用

题目381631. 最小体力消耗路径

解题思路:二分+bfs。

C++代码如下,

class Solution {
public:
    int minimumEffortPath(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int left = 0;
        int min_x = grid[0][0];
        int max_x = grid[0][0];
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                min_x = min(min_x, grid[i][j]);
                max_x = max(max_x, grid[i][j]);
            }
        }
        int right = max_x - min_x;
        int res = -1;
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};

        function<bool(int)> check =[&] (int mid) -> bool {
            vector<vector<bool>> st(n, vector<bool>(m, false));
            queue<pair<int,int>> q;
            q.push(make_pair(0,0));
            st[0][0] = true;
            while (!q.empty()) {
                auto [i,j] = q.front();
                if (i == n-1 && j == m-1) { //提前返回
                    return true;
                }
                q.pop();
                for (int k = 0; k < 4; ++k) {
                    int ni = i + dirs[k][0];
                    int nj = j + dirs[k][1];
                    if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                    if (st[ni][nj]) continue;
                    if (abs(grid[ni][nj]-grid[i][j]) > mid) continue;
                    q.push(make_pair(ni,nj));
                    st[ni][nj] = true;
                }
            }
            return false;
        };

        while (left <= right) {
            int mid = (left + right) / 2;
            if (check(mid)) {
                //求最小值
                res = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return res;
    }
};

python3代码如下,

class Solution:
    def minimumEffortPath(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])
        left = 0
        minx = grid[0][0]
        maxx = grid[0][0]
        for i in range(n):
            for j in range(m):
                x = grid[i][j]
                minx = min(minx, x)
                maxx = max(maxx, x)
        right = maxx - minx 
        
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]

        def check(mid: int) -> bool:
            q = collections.deque([])
            q.append([0,0])
            st = [[False] * m for _ in range(n)]
            st[0][0] = True 
            while len(q) > 0:
                i, j = q.popleft()
                if i == n-1 and j == m-1:
                    return True 
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if st[ni][nj]:
                        continue 
                    if abs(grid[ni][nj]-grid[i][j]) > mid:
                        continue 
                    q.append([ni,nj])
                    st[ni][nj] = True 
            return False 

        res = -1
        while left <= right:
            mid = (left + right) // 2
            if check(mid):
                #求最小值
                res = mid 
                right = mid - 1
            else:
                left = mid + 1
        return res 

题目39778. 水位上升的泳池中游泳

解题思路:二分+bfs。

C++代码如下,

class Solution {
public:
    int swimInWater(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int left = grid[0][0];
        int max_x = grid[0][0];
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                max_x = max(max_x, grid[i][j]);
            }
        }
        int right = max_x;
        int res = -1;
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};

        function<bool(int)> check =[&] (int mid) -> bool {
            vector<vector<bool>> st(n, vector<bool>(m, false));
            queue<pair<int,int>> q;
            q.push(make_pair(0,0));
            st[0][0] = true;
            while (!q.empty()) {
                auto [i,j] = q.front();
                if (i == n-1 && j == m-1) { //提前返回
                    return true;
                }
                q.pop();
                for (int k = 0; k < 4; ++k) {
                    int ni = i + dirs[k][0];
                    int nj = j + dirs[k][1];
                    if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                    if (st[ni][nj]) continue;
                    if (grid[ni][nj] > mid) continue;
                    q.push(make_pair(ni,nj));
                    st[ni][nj] = true;
                }
            }
            return false;
        };

        while (left <= right) {
            int mid = (left + right) / 2;
            if (check(mid)) {
                //求最小值
                res = mid;
                right = mid-1;
            } else {
                left = mid + 1;
            }
        }
        return res;
    }
};

python3代码如下,

class Solution:
    def swimInWater(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])
        max_x = grid[0][0]
        for i in range(n):
            for j in range(m):
                max_x = max(max_x, grid[i][j])
        left = grid[0][0]
        right = max_x 
        res = -1
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]

        def check(mid: int) -> bool:
            st = [[False] * m for _ in range(n)]
            q = collections.deque([])
            q.append([0,0])
            st[0][0] = True 
            while len(q) > 0:
                i, j = q.popleft()
                if i == n-1 and j == m-1: #提前返回
                    return True 
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if st[ni][nj]:
                        continue 
                    if grid[ni][nj] > mid:
                        continue 
                    q.append([ni,nj])
                    st[ni][nj] = True 
            return False 

        while left <= right:
            mid = (left + right) // 2
            if check(mid):
                #求最小值
                res = mid
                right = mid - 1
            else:
                left = mid + 1
        return res  

题目40329. 矩阵中的最长递增路径

解题思路:动态规划,计算状态x时,要提前计算能够转移到它的状态。

C++代码如下,

class Solution {
public:
    int longestIncreasingPath(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        vector<vector<int>> coords;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                vector<int> t = {grid[i][j],i,j};
                coords.push_back(t);
            }
        }

        sort(coords.begin(), coords.end());
        //计算答案
        vector<vector<int>> f(n, vector<int>(m, 1)); //状态f[i][j]表示以(i,j)结尾的最长递增序列的长度
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
        int res = 1;
        for (auto coord : coords) {
            int i = coord[1];
            int j = coord[2];
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                if (grid[i][j] >= grid[ni][nj]) continue;
                f[ni][nj] = max(f[ni][nj], f[i][j] + 1);
                res = max(res, f[ni][nj]);
            }
        }
        return res;
    }
};

python3代码如下,

class Solution:
    def longestIncreasingPath(self, grid: List[List[int]]) -> int:
        #严格递增路径
        n, m = len(grid), len(grid[0])
        coords = []
        for i in range(n):
            for j in range(m):
                coords.append([grid[i][j],i,j])
        coords.sort()
        f = [[1] * m for _ in range(n)] #f[i][j]表示以(i,j)为终点的最长递增路径长度
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        res = 1
        for _,i,j in coords:
            for k in range(4):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                if grid[i][j] >= grid[ni][nj]:
                    continue 
                f[ni][nj] = max(f[ni][nj], f[i][j] + 1)
                res = max(res, f[ni][nj])
        return res 

题目411036. 逃离大迷宫

解题思路:

(1)从起点进行bfs,它能访问到结点的数量大于MAX。并且,从终点进行bfs,它能访问到结点的数量大于MAX。此时,返回True。否则,返回False

(2)还有一种情况,即便能够访问到的结点的数量小于等于MAX,也返回True。即,起点能访问到终点。

(3)n = blocked.size(), 有n个障碍物,利用边界,它所能围成的最大面积为(n-1)*n//2。故MAX=(n-1)*n//2

C++代码如下,

class Solution {
public:
    bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {
        int n = 1e6;
        int t = blocked.size();
        const int MAX = (t-1)*t/2;
        set<pair<int,int>> blocked_set;
        for (auto t : blocked) {
            blocked_set.insert(make_pair(t[0],t[1]));
        }

        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};

        function<bool(vector<int>,vector<int>)> check =[&](vector<int> snode, vector<int> enode) -> bool {
            set<pair<int,int>> visited;
            queue<pair<int,int>> q;
            q.push(make_pair(snode[0],snode[1]));
            visited.insert(make_pair(snode[0],snode[1]));
            while (!q.empty() && visited.size() <= MAX) {
                auto [i,j] = q.front();
                if (i == enode[0] && j == enode[1]) return true; //特判
                q.pop();
                for (int k = 0; k < 4; ++k) {
                    int ni = i + dirs[k][0];
                    int nj = j + dirs[k][1];
                    if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
                    pair<int,int> next_node(ni,nj);
                    if (visited.find(next_node) != visited.end()) continue;
                    if (blocked_set.find(next_node) != blocked_set.end()) continue;
                    q.push(next_node);
                    visited.insert(next_node);
                }
            }
            return visited.size() > MAX;
        };

        return check(source,target) && check(target,source); 
    }
};

python3代码如下,

class Solution:
    def isEscapePossible(self, blocked: List[List[int]], source: List[int], target: List[int]) -> bool:
        #从起点进行bfs,它能访问到结点的数量大于MAX。并且,从终点进行bfs,它能访问到结点的数量大于MAX。此时,返回True。否则,返回False。
        #n = blocked.size(), 有n个障碍物,利用边界,它所能围成的最大面积为(n-1)*n//2。故MAX=(n-1)*n//2。
        #还有一种情况,即便能够访问到的结点的数量小于等于MAX,也返回True。即,起点能访问到终点。
        n = int(1e6)
        t = len(blocked)
        blocked = [tuple(x) for x in blocked]
        blocked = set(blocked)
        MAX = t*(t-1)//2

        dirs = [[-1,0],[0,-1],[1,0],[0,1]]

        def check(snode: list, enode: list) -> bool:
            snode = tuple(snode)
            enode = tuple(enode)
            q = collections.deque([])
            visited = set()
            q.append(snode)
            visited.add(snode)
            while len(q) > 0 and len(visited) <= MAX:
                i, j = q.popleft()
                if (i,j) == enode:
                    return True 
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= n:
                        continue 
                    if (ni,nj) in visited:
                        continue 
                    if (ni,nj) in blocked:
                        continue 
                    q.append((ni,nj))
                    visited.add((ni,nj))
            return len(visited) > MAX 

        return check(source, target) and check(target, source) 

题目42864. 获取所有钥匙的最短路径

解题思路:状态压缩+bfs。前两维表示位置,后一维表示当前拥有的钥匙组合。

C++代码如下,

class Solution {
public:
    int shortestPathAllKeys(vector<string>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int t = 0; //钥匙的数目
        int si = 0, sj = 0; //起点
        string str1 = "abcdef"; //钥匙串
        string str2 = "ABCDEF"; //锁串
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == '@') {
                    si = i;
                    sj = j;
                } else if (str1.find(grid[i][j]) != string::npos) {
                    t += 1;
                }
            }
        }
        //总共有t把钥匙
        int p = 1 << t; //第三维的最大值
        vector<vector<vector<int>>> d(n, vector<vector<int>>(m, vector<int>(p, -1)));
        queue<vector<int>> q;
        vector<int> vec1 = {si,sj,0};
        q.push(vec1);
        d[si][sj][0] = 0;

        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
        
        while (!q.empty()) {
            auto vec1 = q.front();
            q.pop();
            int i = vec1[0];
            int j = vec1[1];
            int s = vec1[2];

            if (s == p-1) { //返回答案
                return d[i][j][s];
            }

            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) { //超过边界了,跳过
                    continue;
                }
                if (grid[ni][nj] == '#') { //是一堵墙,跳过
                    continue;
                }
                char c = grid[ni][nj];
                int ns = s;
                if (str1.find(c) != string::npos) { //是一把钥匙
                    int idx = c - 'a';
                    if ((ns >> idx) & 1) { //之前拾取过这把钥匙
                        //pass
                    } else {
                        ns += 1 << idx;
                    }
                }
                if (d[ni][nj][ns] != -1) { //这个状态计算过了,跳过
                    continue;
                }
                if (str2.find(c) != string::npos) { //是一把锁
                    int idx = c - 'A';
                    if ((ns >> idx) & 1) { //有对应的钥匙
                        //pass
                    } else { //没有对应的钥匙,跳过
                        continue;
                    }
                }
                vector<int> vec2 = {ni,nj,ns};
                q.push(vec2);
                d[ni][nj][ns] = d[i][j][s] + 1;
            }
        }
        return -1;
    }
};

python3代码如下,

class Solution:
    def shortestPathAllKeys(self, grid: List[str]) -> int:
        n, m = len(grid), len(grid[0]) 
        #grid[i][j] 只含有 '.', '#', '@', 'a'-'f' 以及 'A'-'F'
        #返回获取所有钥匙所需要的移动的最少次数
        #如果无法获取所有钥匙,返回 -1
        #使用状态压缩求解
        t = 0 #钥匙的数目
        si, sj = -1, -1
        for i in range(n):
            for j in range(m):
                if grid[i][j] == '@':
                    si, sj = i, j
                elif grid[i][j] in "abcdef":
                    t += 1

        p = 1 << t #t最大是6
        d = [[[-1] * p for _ in range(m)] for _ in range(n)] 
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        
        q = collections.deque([])
        q.append([si,sj,0])
        d[si][sj][0] = 0
        while len(q) > 0:
            tot = len(q)
            while tot > 0:
                tot -= 1
                
                i,j,s = q.popleft()
                if s == p-1: #提前返回
                    return d[i][j][s] 
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m: #超过边界
                        continue 
                    if grid[ni][nj] == '#': #是一堵墙
                        continue 
                    ns = s 
                    if grid[ni][nj] in "abcdef": #是一把钥匙
                        idx = ord(grid[ni][nj]) - ord('a')
                        if (ns >> idx) & 1:
                            pass #这把钥匙已经获取过了
                        else:
                            ns += (1 << idx) 
                    if d[ni][nj][ns] != -1: #这个状态计算过了
                        continue 
                    if grid[ni][nj] in "ABCDEF": #是一把锁
                        idx = ord(grid[ni][nj]) - ord('A')
                        if (ns >> idx) & 1:
                            pass #有钥匙
                        else: #没有钥匙
                            continue 
                    d[ni][nj][ns] = d[i][j][s] + 1
                    q.append([ni,nj,ns])
        
        return -1

题目431263. 推箱子

解题思路:记录当前的箱子的位置(bi,bj)、当前人的位置(pi,pj),遍历四个方向,考虑下一步箱子的位置(nbi,nbj),而下一步人的位置(npi,npj)可以由下一步箱子(nbi,nbj)的位置推导出来。然后需要判断,人能不能从(pi,pj)走到(npi,npj),此时箱子在(bi,bj)

C++代码如下,

class Solution {
public:
    int minPushBox(vector<vector<char>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
        int bi = -1, bj = -1; //箱子的初始位置
        int pi = -1, pj = -1; //人的初始位置
        int ei = -1, ej = -1; //目标位置
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == 'B') {
                    bi = i;
                    bj = j;
                } else if (grid[i][j] == 'S') {
                    pi = i;
                    pj = j;
                } else if (grid[i][j] == 'T') {
                    ei = i;
                    ej = j;
                }
            }
        }

        function<bool(int,int,int,int,int,int)> check =[&] (int bi, int bj, int pi, int pj, int npi, int npj) -> bool {
            vector<vector<bool>> st(n, vector<bool>(m, false));
            queue<pair<int,int>> q;
            q.push(make_pair(pi,pj));
            st[pi][pj] = true;
            while (!q.empty()) {
                auto [i,j] = q.front();
                if (i == npi && j == npj) { //走到了终点,返回true
                    return true;
                }
                q.pop();
                for (int k = 0; k < 4; ++k) {
                    int ni = i + dirs[k][0];
                    int nj = j + dirs[k][1];
                    if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                    if (grid[ni][nj] == '#' || (ni == bi && nj == bj)) continue;
                    if (st[ni][nj]) continue;
                    q.push(make_pair(ni,nj));
                    st[ni][nj] = true;
                }
            }
            return false;
        };

        vector<vector<vector<vector<int>>>> d(n, vector<vector<vector<int>>>(m, vector<vector<int>>(n, vector<int>(m, -1))));
        queue<vector<int>> q;
        q.push({bi,bj,pi,pj});
        d[bi][bj][pi][pj] = 0;
        while (!q.empty()) {
            auto t = q.front();
            q.pop();
            int bi = t[0];
            int bj = t[1]; //箱子的当前位置(bi,bj)
            int pi = t[2];
            int pj = t[3]; //人的当前位置(pi,pj)
            if (bi == ei && bj == ej) {
                return d[bi][bj][pi][pj];
            }
            for (int k = 0; k < 4; ++k) {
                int nbi = bi + dirs[k][0];
                int nbj = bj + dirs[k][1]; //箱子的下一步位置(nbi,nbj)
                int k1 = (k+2)%4;
                int npi = bi + dirs[k1][0];
                int npj = bj + dirs[k1][1]; //人的下一步位置(npi,npj)
                if (nbi < 0 || nbi >= n || nbj < 0 || nbj >= m) continue;
                if (npi < 0 || npi >= n || npj < 0 || npj >= m) continue;
                if (grid[nbi][nbj] == '#') continue;
                if (grid[npi][npj] == '#') continue;
                if (d[nbi][nbj][npi][npj] != -1) continue;
                //箱子位置在(bi,bj),人能从(pi,pj)走到(npi,npj)
                if (!check(bi,bj,pi,pj,npi,npj)) continue;
                q.push({nbi,nbj,npi,npj});
                d[nbi][nbj][npi][npj] = d[bi][bj][pi][pj] + 1;
            }
        }
        return -1;
    }
};

python3代码如下,

#我的题解
class Solution:
    def minPushBox(self, grid: List[List[str]]) -> int:
        #返回将箱子推到目标位置的最小推动次数
        n, m = len(grid), len(grid[0]) 
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        
        def check(bi,bj,pi,pj,npi,npj): #箱子的位置在(bi,bj),当前人的位置在(pi,pj),人下一步的位置在(npi,npj),能走到目标处
            st = [[False] * m for _ in range(n)]
            q = collections.deque([])
            q.append((pi,pj))
            st[pi][pj] = True 
            while len(q) > 0:
                i,j = q.popleft()
                if i == npi and j == npj:
                    return True 
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if grid[ni][nj] == '#' or (ni == bi and nj == bj):
                        continue 
                    if st[ni][nj]:
                        continue 
                    q.append((ni,nj)) 
                    st[ni][nj] = True 
            return False 

        snode = None #人的位置
        bnode = None #箱子的位置
        enode = None #目标的位置
        for i in range(n):
            for j in range(m):
                if grid[i][j] == 'S':
                    snode = (i,j)
                elif grid[i][j] == 'B':
                    bnode = (i,j)
                elif grid[i][j] == 'T':
                    enode = (i,j)
                    
        #计算答案
        d = [[[[-1] * m for _ in range(n)] for _ in range(m)] for _ in range(n)]
        q = collections.deque([])
        q.append((bnode[0],bnode[1],snode[0],snode[1]))
        d[bnode[0]][bnode[1]][snode[0]][snode[1]] = 0
        while len(q) > 0:
            bi,bj,pi,pj = q.popleft() #箱子的位置为(bi,bj),当前人的位置为(pi,pj)
            if (bi,bj) == enode: #返回答案
                return d[bi][bj][pi][pj]  
            for k in range(4):
                nbi = bi + dirs[k][0] #箱子下一步的位置(nbi,nbj)
                nbj = bj + dirs[k][1] 
                k1 = (k+2)%4 
                npi = bi + dirs[k1][0] #人下一步的位置(npi,npj)
                npj = bj + dirs[k1][1] 
                if nbi < 0 or nbi >= n or nbj < 0 or nbj >= m:
                    continue 
                if npi < 0 or npi >= n or npj < 0 or npj >= m:
                    continue 
                if grid[nbi][nbj] == '#':
                    continue 
                if grid[npi][npj] == '#':
                    continue 
                if d[nbi][nbj][npi][npj] != -1:
                    continue 
                if not check(bi,bj,pi,pj,npi,npj): #箱子的位置在(bi,bj),当前人的位置在(pi,pj),人下一步的位置在(npi,npj),能走到目标处
                    continue 
                q.append((nbi,nbj,npi,npj))
                d[nbi][nbj][npi][npj] = d[bi][bj][pi][pj] + 1
        return -1 
#官方题解
class Solution:
    def minPushBox(self, grid: List[List[str]]) -> int:
        m = len(grid)
        n = len(grid[0]) 
        sx, sy, bx, by = None, None, None, None 
        for x in range(m):
            for y in range(n):
                if grid[x][y] == 'S':
                    sx = x 
                    sy = y 
                elif grid[x][y] == 'B':
                    bx = x 
                    by = y 
        
        #不越界且不在墙上
        def ok(x, y):
            return (0 <= x < m and 0 <= y < n and grid[x][y] != '#')
        
        d = [0,-1,0,1,0]
        dp = [[float('inf')] * (m*n) for _ in range(m*n)]
        dp[sx*n+sy][bx*n+by] = 0 #初始状态的推动次数为0
        q = deque([(sx*n+sy,bx*n+by)])
        while q:
            q1 = deque()
            while q:
                s1, b1 = q.popleft()
                sx1, sy1 = s1 // n, s1 % n 
                bx1, by1 = b1 // n, b1 % n 
                if grid[bx1][by1] == 'T': #箱子已被推到目标处
                    return dp[s1][b1] 
                for i in range(4):
                    sx2, sy2 = sx1 + d[i], sy1 + d[i+1]
                    s2 = sx2 * n + sy2 
                    if not ok(sx2, sy2): #玩家位置不合法
                        continue 
                    if sx2 == bx1 and sy2 == by1: #推动箱子
                        bx2, by2 = bx1 + d[i], by1 + d[i+1]
                        b2 = bx2 * n + by2 
                        if not ok(bx2, by2) or dp[s2][b2] <= dp[s1][b1] + 1:
                            continue 
                        dp[s2][b2] = dp[s1][b1] + 1
                        q1.append((s2,b2))
                    else:
                        if dp[s2][b1] <= dp[s1][b1]: #状态已访问
                            continue 
                        dp[s2][b1] = dp[s1][b1]
                        q.append((s2,b1))
            q, q1 = q1, q 
        return -1 

题目442258. 逃离火灾

解题思路:二分+两遍bfs。

C++代码如下,

class Solution {
public:
    int maximumMinutes(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
        vector<vector<int>> d(n, vector<int>(m, -1)); //d[i][j]表示火走到(i,j)处所花费的时间
        queue<pair<int,int>> q;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == 1) {
                    q.push(make_pair(i,j));
                    d[i][j] = 0;
                }
            } 
        }
        while (!q.empty()) {
            auto [i,j] = q.front();
            q.pop();
            for (int k = 0; k < 4; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                if (grid[ni][nj] == 2) continue;
                if (d[ni][nj] != -1) continue;
                q.push(make_pair(ni,nj));
                d[ni][nj] = d[i][j] + 1;
            }
        }

        function<bool(int)> check =[&] (int mid) -> bool {
            queue<pair<int,int>> q;
            vector<vector<bool>> st(n, vector<bool>(m, false));
            q.push(make_pair(0,0));
            st[0][0] = true;
            int level = mid;
            while (!q.empty()) {
                int tot = q.size();
                while (tot--) {
                    auto [i,j] = q.front(); //(i,j)的时间为level
                    q.pop();
                    if (i == n-1 && j == m-1) { //走到了终点,返回true
                        return true;
                    }
                    for (int k = 0; k < 4; ++k) {
                        int ni = i + dirs[k][0];
                        int nj = j + dirs[k][1];
                        if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                        if (grid[ni][nj] == 2) continue;
                        if (ni == n-1 && nj == m-1) {
                            if (d[ni][nj] != -1 && level+1 > d[ni][nj]) continue;
                        } else {
                            if (d[ni][nj] != -1 && level+1 >= d[ni][nj]) continue;
                        }
                        if (st[ni][nj]) continue;
                        q.push(make_pair(ni,nj));
                        st[ni][nj] = true;
                    }
                }
                level += 1;
            }
            return false;
        };

        int left = 0;
        int right = 1e9;
        int res = -1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (check(mid)) {
                res = mid;
                //求最大值
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return res;
    }
};

python3代码如下,

class Solution:
    def maximumMinutes(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        d = [[-1] * m for _ in range(n)] #d[i][j]表示火达到(i,j)处的时间
        q = collections.deque([])
        for i in range(n):
            for j in range(m):
                if grid[i][j] == 1:
                    q.append((i,j))
                    d[i][j] = 0
        while len(q) > 0:
            i, j = q.popleft()
            for k in range(4):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                if grid[ni][nj] == 2:
                    continue 
                if d[ni][nj] != -1:
                    continue 
                q.append((ni,nj))
                d[ni][nj] = d[i][j] + 1
        
        def check(mid: int) -> bool:
            st = [[False] * m for _ in range(n)]
            q = collections.deque([])
            q.append((0,0))
            st[0][0] = True 
            level = mid
            while len(q) > 0:
                tot = len(q)
                while tot > 0:
                    tot -= 1
                    i,j = q.popleft() #(i,j)的时间是level
                    if i == n-1 and j == m-1: #能到达安全屋,返回True
                        return True 
                    for k in range(4):
                        ni = i + dirs[k][0]
                        nj = j + dirs[k][1]
                        if ni < 0 or ni >= n or nj < 0 or nj >= m:
                            continue 
                        if grid[ni][nj] == 2:
                            continue 
                        #注意,如果你到达安全屋后,火马上到了安全屋,这视为你能够安全到达安全屋
                        if ni == n-1 and nj == m-1:
                            if d[ni][nj] != -1 and level + 1 > d[ni][nj]: #火已经到达(ni,nj)了
                                continue 
                        else:
                            if d[ni][nj] != -1 and level + 1 >= d[ni][nj]: #火已经到达(ni,nj)了
                                continue                             
                        if st[ni][nj]:
                            continue 
                        q.append((ni,nj))
                        st[ni][nj] = True 
                level += 1
            return False 

        left = 0
        right = int(1e9)
        res = -1
        while left <= right:
            mid = (left + right) // 2
            if check(mid):
                #求最大值
                res = mid
                left = mid + 1
            else:
                right = mid - 1 
        return res 

题目452556. 二进制矩阵中翻转最多一次使路径不连通

解题思路:本质是找到两条毫不相关的路径,能从起点走到终点。

C++代码如下,

class Solution {
public:
    bool isPossibleToCutPath(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int dirs[2][2] = {{1,0},{0,1}};

        auto bfs =[&] (int i, int j, vector<vector<bool>>& st, bool& res, vector<pair<int,int>>& path) -> void {
            queue<pair<int,int>> q;
            q.push(make_pair(0,0));
            st[0][0] = true;
            map<pair<int,int>,pair<int,int>> map_y_x;
            while (!q.empty()) {
                auto [i,j] = q.front();
                q.pop();
                for (int k = 0; k < 2; ++k) {
                    int ni = i + dirs[k][0];
                    int nj = j + dirs[k][1];
                    if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                    if (grid[ni][nj] != 1) continue;
                    if (st[ni][nj]) continue;
                    q.push(make_pair(ni,nj));
                    st[ni][nj] = true;
                    map_y_x[make_pair(ni,nj)] = make_pair(i,j);
                }
            }

            if (st[n-1][m-1]) { //能走到终点
                res = true;
                path.clear();
                pair<int,int> node(n-1,m-1);
                while (map_y_x.find(node) != map_y_x.end()) {
                    path.push_back(node);
                    node = map_y_x[node];
                }
            } else { //不能走到终点
                res = false;
                path.clear();
            }
            return;
        };
        
        bool res1 = false;
        vector<pair<int,int>> path1;
        vector<vector<bool>> st(n, vector<bool>(m, false));
        bfs(0,0,st,res1,path1);
        if (res1) { //能走到终点
            vector<vector<bool>> st(n, vector<bool>(m, false));
            for (auto [i,j] : path1) {
                st[i][j] = true;
            }
            st[n-1][m-1] = false;

            // //输出st
            // for (int i = 0; i < n; ++i) {
            //     for (int j = 0; j < m; ++j) {
            //         cout << st[i][j] << " ";
            //     }
            //     cout << endl;
            // }

            bool res2 = false;
            vector<pair<int,int>> path2;
            bfs(0,0,st,res2,path2);

            // //输出path2
            // for (auto [i,j] : path2) {
            //     cout << "(" << i << "," << j << "),"; 
            // }           
            // cout << endl;

            if (res2) { //能走到终点
                return false;
            } else { //不能走到终点
                return true;
            }
        } else { //不能走到终点
            return true;
        }
    }
};

python3代码如下,

class Solution:
    def isPossibleToCutPath(self, grid: List[List[int]]) -> bool:
        #你可以翻转最多一个格子的值(也可以不翻转)。你不能翻转格子(0, 0)和(m - 1, n - 1)
        n, m = len(grid), len(grid[0])
        dirs = [[1,0],[0,1]]

        def bfs(si: int, sj: int, st: list) -> list:
            q = collections.deque([])
            q.append((si,sj))
            st[si][sj] = True 
            map_y_x = collections.defaultdict(tuple)
            while len(q) > 0:
                i, j = q.popleft()
                for k in range(2):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if grid[ni][nj] != 1:
                        continue 
                    if st[ni][nj]:
                        continue 
                    q.append((ni,nj))
                    st[ni][nj] = True 
                    map_y_x[(ni,nj)] = (i,j)
            
            path = []
            if st[n-1][m-1]:
                node = (n-1,m-1)
                while node in map_y_x:
                    path.append(node)
                    node = map_y_x[node]
                return [True, path]
            else:
                return [False, path]
                
                
        st = [[False] * m for _ in range(n)]
        res1, path1 = bfs(0,0,st)
        if res1: #如果能走到终点
            st = [[False] * m for _ in range(n)]
            for i,j in path1:
                st[i][j] = True 
            st[0][0] = False 
            st[n-1][m-1] = False 
            res2, path2 = bfs(0,0,st)
            if res2: #如果能走到终点
                return False 
            else: #不能走到终点
                return True 
            
        else: #不能走到终点
            return True 

题目462577. 在网格图中访问一个格子的最少时间

解题思路:二分+bfs。二分到达终点的时间。

C++代码如下,

class Solution {
public:
    int minimumTime(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};

        if (grid[0][1] > 1 && grid[1][0] > 1) return -1; //特判不能走到终点的情况

        int left = 0;
        int right = 1e6;
        int res = -1;

        function<bool(int)> check =[&] (int mid) -> bool {
            //终点的时间为mid,它能从终点走到起点。
            if (mid < grid[n-1][m-1]) return false; //特判
            vector<vector<bool>> st(n, vector<bool>(m, false));
            queue<pair<int,int>> q;
            q.push(make_pair(n-1,m-1));
            st[n-1][m-1] = true;
            int level = mid;
            while (!q.empty()) {
                int tot = q.size();
                while (tot--) {
                    auto [i,j] = q.front(); //(i,j)的时间为level
                    q.pop();
                    if (i == 0 && j == 0) return true; //返回答案
                    for (int k = 0; k < 4; ++k) {
                        int ni = i + dirs[k][0];
                        int nj = j + dirs[k][1];
                        if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                        if (level-1 < grid[ni][nj]) continue;
                        if (st[ni][nj]) continue;
                        q.push(make_pair(ni,nj));
                        st[ni][nj] = true;
                    }
                }
                level -= 1;
            }
            return false;
        };

        while (left <= right) {
            int mid = (left + right) / 2;
            if (check(mid)) {
                //求最小值
                res = mid;
                right = mid-1;
            } else {
                left = mid+1;
            }
        }
        res = res + (res-n-m+2)%2;
        return res;
    }
};

python3代码如下,

#二分+bfs解法
class Solution:
    def minimumTime(self, grid: List[List[int]]) -> int:
        #二分到达终点的时间
        #到达(i,j)的最短时间的奇偶性与(i+j)的奇偶性一致
        n, m = len(grid), len(grid[0])
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]
        if grid[0][1] > 1 and grid[1][0] > 1: #特判无法到达终点的情况
            return -1 
        left = 0
        right = int(1e6)
        res = -1

        def check(mid: int) -> bool:
            if grid[n-1][m-1] > mid: #特判
                return False 
            #到达终点的时间为mid,它能从终点走到起点
            st = [[False] * m for _ in range(n)]
            q = collections.deque([])
            q.append((n-1,m-1))
            st[n-1][m-1] = True 
            level = mid 
            while len(q) > 0:
                tot = len(q) 
                while tot > 0:
                    tot -= 1
                    i,j = q.popleft() #(i,j)的时间为level
                    if i == 0 and j == 0: #能走到起点,返回答案
                        return True 
                    for k in range(4):
                        ni = i + dirs[k][0]
                        nj = j + dirs[k][1]
                        if ni < 0 or ni >= n or nj < 0 or nj >= m:
                            continue 
                        if st[ni][nj]:
                            continue 
                        if level-1 < grid[ni][nj]:
                            continue 
                        q.append((ni,nj))
                        st[ni][nj] = True 
                level -= 1
            return False 

        while left <= right:
            mid = (left + right) // 2
            if check(mid):
                #求最小值
                res = mid
                right = mid-1
            else:
                left = mid+1
        res = res + (res-(n-1)-(m-1))%2
        return res  
#dijkstra解法
class Solution:
    def minimumTime(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        if grid[0][1] > 1 and grid[1][0] > 1: 
            return -1
        
        dis = [[inf] * n for _ in range(m)] 
        dis[0][0] = 0 
        h = [(0,0,0)]
        while True:
            d, i, j = heappop(h)
            if d > dis[i][j]:
                continue 
            if i == m-1 and j == n-1:
                return d 
            for x, y in (i+1,j),(i-1,j),(i,j+1),(i,j-1):
                if 0 <= x < m and 0 <= y < n:
                    nd = max(d+1, grid[x][y])
                    nd += (nd-x-y)%2 #nd必须和x+y同奇偶
                    if nd < dis[x][y]:
                        dis[x][y] = nd 
                        heappush(h,(nd,x,y))
                        

题目472617. 网格图中最少访问的格子数

解题思路:最小堆+贪心。

C++代码如下,

typedef pair<int,int> PII;
const int inf = 1e6;

class Solution {
public:
    int minimumVisitedCells(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int f = inf;
        vector<priority_queue<PII,vector<PII>,greater<PII>>> col_hs(m, priority_queue<PII,vector<PII>,greater<PII>>());
        for (int i = 0; i < n; ++i) {
            auto row = grid[i];
            priority_queue<PII,vector<PII>,greater<PII>> row_h;
            for (int j = 0; j < m; ++j) {
                int g = row[j];
                priority_queue<PII,vector<PII>,greater<PII>>& col_h = col_hs[j];
                while (!row_h.empty() && row_h.top().second < j) {
                    row_h.pop();
                }
                while (!col_h.empty() && col_h.top().second < i) {
                    col_h.pop();
                }
                f = inf;
                if (i == 0 && j == 0) f = 1;
                if (!row_h.empty()) {
                    f = row_h.top().first + 1;
                }
                if (!col_h.empty()) {
                    int t = col_h.top().first + 1;
                    f = min(f, t);
                }
                if (g > 0 && f < inf) {
                    row_h.push(make_pair(f,g+j));
                    col_h.push(make_pair(f,g+i));
                }
            }
        }
        return f < inf ? f : -1;
    }
};

python3代码如下,

#my ans
class Solution:
    def minimumVisitedCells(self, grid: List[List[int]]) -> int:
        n, m = len(grid), len(grid[0])
        col_hs = [[] for _ in range(m)]
        for i,row in enumerate(grid):
            row_h = [] #第i行的最小堆
            for j, (g,col_h) in enumerate(zip(row,col_hs)):
                while row_h and row_h[0][1] < j:
                    heappop(row_h)
                while col_h and col_h[0][1] < i:
                    heappop(col_h) 
                f = inf if i or j else 1
                if row_h:
                    f = row_h[0][0] + 1
                if col_h:
                    f = min(f, col_h[0][0] + 1)
                if g > 0 and f < inf:
                    heappush(row_h, (f,g+j))
                    heappush(col_h, (f,g+i))
        return f if f < inf else -1 
#最小堆+贪心
class Solution:
    def minimumVisitedCells(self, grid: List[List[int]]) -> int:
        col_heaps = [[] for _ in grid[0]]
        for i, row in enumerate(grid):
            row_h = []
            for j, (g, col_h) in enumerate(zip(row,col_heaps)):
                while row_h and row_h[0][1] < j:
                    heappop(row_h)
                while col_h and col_h[0][1] < i:
                    heappop(col_h) 
                f = inf if i or j else 1 
                if row_h:
                    f = row_h[0][0] + 1
                if col_h:
                    f = min(f, col_h[0][0] + 1)
                if g and f < inf:
                    heappush(row_h, (f, g+j))
                    heappush(col_h, (f, g+i))
        return f if f < inf else -1 

题目48LCP 13. 寻宝

解题思路:

(1)计算起点-石块-第i个机关的最小距离d0[i]

(2)计算第i个机关-石块-第j个机关的最小距离d1[i][j]

(3)计算第i个机关-终点的最小距离d2[i]

(4)动态规划,当前已经访问了第i个机关,已访问的机关的状态为mask,此时的最小距离为dp[i][mask]

(5)注意,动态规划转移顺序,

for (int mask = 0; mask < (1 << nb); ++mask) {
    for (int i = 0; i < nb; ++i) {
        for (int j = 0; j < nb; ++j) {
            //process
        }
    }
}

C++代码如下,

//mine
class Solution {
public:
    void get_d(vector<vector<int>>& d, const int sx, const int sy, const vector<string>& grid) {
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
        int n = grid.size();
        int m = grid[0].size();
        queue<pair<int,int>> q;
        q.push({sx,sy});
        d[sx][sy] = 0;
        while (!q.empty()) {
            auto [x,y] = q.front();
            q.pop();
            for (int k = 0; k < 4; ++k) {
                int nx = x + dirs[k][0];
                int ny = y + dirs[k][1];
                if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                if (d[nx][ny] != -1) continue;
                if (grid[nx][ny] == '#') continue;
                q.push({nx,ny});
                d[nx][ny] = d[x][y] + 1;
            }
        }
        return;
    }

    void get_d_stone(vector<vector<vector<int>>>& d_stone, const vector<string>& grid, const vector<pair<int,int>>& stones) {
        int n = grid.size();
        int m = grid[0].size();
        int ns = stones.size();
        for (int i = 0; i < ns; ++i) {
            auto [sx,sy] = stones[i];
            vector<vector<int>> d(n, vector<int>(m, -1)); //用-1初始化
            get_d(d, sx, sy, grid);
            d_stone[i] = d;
        }
        return;
    }

    void get_d0(vector<int>& d0, const vector<vector<vector<int>>>& d_stone, const vector<vector<int>>& d_start, const vector<pair<int,int>>& stones, const vector<pair<int,int>>& buttons) {

        int ns = stones.size();
        int nb = buttons.size();

        for (int i = 0; i < nb; ++i) {
            //计算起点-石块-第i个机关的最小距离d0[i]
            auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
            int ans = -1;
            for (int j = 0; j < ns; ++j) {
                auto [x2, y2] = stones[j]; //第j个石头的坐标(x2,y2)
                if (d_start[x2][y2] == -1 || d_stone[j][x1][y1] == -1) continue;
                int curr = d_start[x2][y2] + d_stone[j][x1][y1];
                if (ans == -1) {
                    ans = curr;
                }
                else {
                    ans = min(ans, curr);
                }
            }
            d0[i] = ans;
        }
        return;
    }

    void get_d1(vector<vector<int>>& d1, const vector<vector<vector<int>>>& d_stone, const vector<pair<int,int>>& buttons, const vector<pair<int,int>>& stones) {

        int ns = stones.size();
        int nb = buttons.size();
        for (int i = 0; i < nb; ++i) {
            auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
            for (int j = i+1; j < nb; ++j) {
                //计算d1[i][j]
                auto [x2, y2] = buttons[j]; //第j个机关的坐标(x2,y2)
                int ans = -1;
                for (int k = 0; k < ns; ++k) {
                    if (d_stone[k][x1][y1] == -1 || d_stone[k][x2][y2] == -1) continue;
                    int curr = d_stone[k][x1][y1] + d_stone[k][x2][y2];
                    if (ans == -1) {
                        ans = curr;
                    } else {
                        ans = min(ans, curr);
                    }
                }
                d1[i][j] = d1[j][i] = ans;
            }
        }

        return;
    }

    void get_d2(vector<int>& d2, const vector<pair<int,int>>& buttons, const int ex, const int ey, const vector<string>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int nb = buttons.size();
        int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};

        queue<pair<int,int>> q;
        q.push({ex,ey});
        vector<vector<int>> d(n, vector<int>(m, -1));
        d[ex][ey] = 0;
        while (!q.empty()) {
            auto [x, y] = q.front();
            q.pop();
            for (int k = 0; k < 4; ++k) {
                int nx = x + dirs[k][0];
                int ny = y + dirs[k][1];
                if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                if (grid[nx][ny] == '#') continue;
                if (d[nx][ny] != -1) continue;
                d[nx][ny] = d[x][y] + 1;
                q.push({nx,ny}); 
            }
        }

        for (int i = 0; i < nb; ++i) {
            auto [x, y] = buttons[i];
            d2[i] = d[x][y];
        }
        
        return;
    }

    int minimalSteps(vector<string>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        vector<pair<int,int>> stones; //石头的坐标
        vector<pair<int,int>> buttons; //机关的坐标
        int sx = -1, sy = -1; //起点的坐标
        int ex = -1, ey = -1; //终点的坐标
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == 'O') {
                    stones.push_back({i,j});
                } else if (grid[i][j] == 'M') {
                    buttons.push_back({i,j});
                } else if (grid[i][j] == 'S') {
                    sx = i;
                    sy = j;
                } else if (grid[i][j] == 'T') {
                    ex = i;
                    ey = j;
                }
            }
        }

        //检查起点和终点对不对
        //cout << "sx = " << sx << ", sy = " << sy << ", ex = " << ex << ", ey = " << ey << endl;

        //检查石头的坐标
        // for (int i = 0; i < stones.size(); ++i) {
        //     auto [x,y] = stones[i];
        //     cout << "i = " << i << ", x = " << x << ", y = " << y << endl; 
        // }

        //检查机关的坐标
        // for (int i = 0; i < buttons.size(); ++i) {
        //     auto [x,y] = buttons[i];
        //     cout << "i = " << i << ", x = " << x << ", y = " << y << endl;
        // }

        int ns = stones.size(); //石块的数目ns
        int nb = buttons.size(); //机关的数目nb

        //计算第i个石块到坐标(x,y)的最小距离d_stone[i][x][y]
        vector<vector<vector<int>>> d_stone(ns, vector<vector<int>>(n, vector<int>(m, -1))); 
        //不能到达的距离初始化为-1
        get_d_stone(d_stone, grid, stones);

        //存在机关到达不了石块,则返回-1
        for (int i = 0; i < nb; ++i) {
            auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
            bool flag = false;
            for (int j = 0; j < ns; ++j) {
                if (d_stone[j][x1][y1] != -1) { //第j个石块能够抵达坐标(x1,y1)
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                return -1; 
            }
        }


        //检查d_stone
        // for (int i = 0; i < ns; ++i) {
        //     cout << "====start i = " << i << endl;
        //     for (int x = 0; x < n; ++x) {
        //         for (int y = 0; y < m; ++y) {
        //             cout << d_stone[i][x][y] << ",";
        //         }
        //         cout << endl;
        //     }
        // }

        //计算起点到坐标(x,y)的最小距离d_start[x][y]
        vector<vector<int>> d_start(n, vector<int>(m, -1));
        get_d(d_start, sx, sy, grid);

        //存在机关到达不了起点,则返回-1
        for (int i = 0; i < nb; ++i) {
            auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
            if (d_start[x1][y1] == -1) {
                return -1;
            }
        }

        //机关数目为0,直接返回起点到终点的距离
        if (nb == 0) {
            return d_start[ex][ey];
        }

        //检查d_start
        // for (int x = 0; x < n; ++x) {
        //     for (int y = 0; y < m; ++y) {
        //         cout << d_start[x][y] << ",";
        //     }
        //     cout << endl;
        // }

        //计算起点-石块-第i个机关的最小距离d0[i]
        vector<int> d0(nb, -1); //-1表示不能到达
        get_d0(d0, d_stone, d_start, stones, buttons);

        //检查d0
        // cout << "d0:";
        // for (int i = 0; i < nb; ++i) {
        //     cout << d0[i] << ",";
        // }
        // cout << endl;

        //计算第i个机关-石块-第j个机关的最小距离d1[i][j]
        vector<vector<int>> d1(nb, vector<int>(nb, -1));
        get_d1(d1, d_stone, buttons, stones);

        //检查d1
        // for (int i = 0; i < nb; ++i) {
        //     for (int j = 0; j < nb; ++j) {
        //         cout << d1[i][j] << ",";
        //     }
        //     cout << endl;
        // }

        //计算终点-第i个机关的最小距离d2[i]
        vector<int> d2(nb, -1);
        get_d2(d2, buttons, ex, ey, grid);

        //存在机关达到不了终点,返回-1
        for (int i = 0; i < nb; ++i) {
            if (d2[i] == -1) {
                return -1;
            }
        }

        //检查d2
        // for (int i = 0; i < nb; ++i) {
        //     cout << d2[i] << ",";
        // }
        // cout << endl;

        

        //动态规划
        const int inf = 0x3f3f3f3f;
        vector<vector<int>> dp(nb, vector<int>(1<<nb, -1));

        //初始化
        for (int i = 0; i < nb; ++i) {
            dp[i][1<<i] = d0[i];
        }

        //检查初始化之后的dp
        // for (int i = 0; i < nb; ++i) {
        //     for (int mask = 0; mask < (1<<nb); ++mask) {
        //         cout << dp[i][mask] << ",";
        //     }
        //     cout << endl;
        // }

        //状态转移
        // for (int i = 0; i < nb; ++i) {
        //     for (int mask = 0; mask < (1<<nb); ++mask) {
        //         if ((mask>>i)&1) {
        //             for (int k = 0; k < nb; ++k) {
        //                 if ((mask>>k)&1) {
        //                     if (dp[i][mask] == -1 || dp[i][mask] > dp[k][mask-(1<<i)] + d1[k][i]) {
        //                         if (dp[k][mask-(1<<i)] == -1 || d1[k][i] == -1) continue;
        //                         //cout << "i = " << i << ", mask = " << mask << ", k = " << k << endl;
        //                         //cout << dp[i][mask] << "," << dp[k][mask-(1<<i)] << "," << d1[k][i] << endl;
        //                         dp[i][mask] = dp[k][mask-(1<<i)] + d1[k][i];
        //                     }
        //                 }
        //             }
        //         }
        //     }
        // }

        //状态转移
        for (int mask = 1; mask < (1 << nb); ++mask) {
            for (int i = 0; i < nb; ++i) {
                if (mask & (1<<i)) {
                    for (int j = 0; j < nb; ++j) {
                        if (!(mask & (1 << j))) {
                            int next = mask | (1 << j);
                            if (dp[j][next] == -1 || dp[j][next] > dp[i][mask] + d1[i][j]) {
                                dp[j][next] = dp[i][mask] + d1[i][j];
                            }
                        }
                    }
                }
            }
        }

        //计算最终答案
        int res = -1;
        for (int i = 0; i < nb; ++i) {
            //auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
            if (dp[i][(1<<nb)-1] == -1) continue;
            int ans = dp[i][(1<<nb)-1] + d2[i];
            if (res == -1) {
                res = ans;
            } else {
                res = min(res, ans);
            }
        }
        return res;
    }
};
//官方解答
class Solution {
public:
    int dx[4] = {1, -1, 0, 0};
    int dy[4] = {0, 0, 1, -1};
    int n, m;

    bool inBound(int x, int y) {
        return x >= 0 && x < n && y >= 0 && y < m;
    }

    vector<vector<int>> bfs(int x, int y, vector<string>& maze) {
        vector<vector<int>> ret(n, vector<int>(m, -1));
        ret[x][y] = 0;
        queue<pair<int,int>> Q;
        Q.push({x,y});
        while (!Q.empty()) {
            auto p = Q.front();
            Q.pop();
            int x = p.first, y = p.second;
            for (int k = 0; k < 4; ++k) {
                int nx = x + dx[k], ny = y + dy[k];
                if (inBound(nx,ny) && maze[nx][ny] != '#' && ret[nx][ny] == -1) {
                    ret[nx][ny] = ret[x][y] + 1;
                    Q.push({nx,ny});
                }
            }
        }
        return ret;
    }

    int minimalSteps(vector<string>& maze) {
        n = maze.size(), m = maze[0].size();
        //机关和石头
        vector<pair<int,int>> buttons, stones;
        //起点和终点
        int sx, sy, tx, ty;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (maze[i][j] == 'M') {
                    buttons.push_back({i,j});
                }
                if (maze[i][j] == 'O') {
                    stones.push_back({i,j});
                }
                if (maze[i][j] == 'S') {
                    sx = i, sy = j;
                }
                if (maze[i][j] == 'T') {
                    tx = i, ty = j;
                }
            }
        }
        int nb = buttons.size();
        int ns = stones.size();
        vector<vector<int>> start_dist = bfs(sx, sy, maze);

        //特判没有机关的情况,直接返回start_dist[tx][ty]
        if (nb == 0) {
            return start_dist[tx][ty];
        }

        //从某个机关到其他机关、起点和终点的最短距离
        vector<vector<int>> dist(nb, vector<int>(nb+2, -1));
        //dist[i][nb+1]:第i个机关-终点的最短距离。
        //dist[i][nb]:起点-石块-第i个机关的最短距离。
        //dist[i][j]:第i个机关-石块-第j个机关的最小距离。

        //中间结果
        vector<vector<vector<int>>> dd(nb);
        //dd[i]:以第i个机关为起点,到每个网格的最短距离。

        for (int i = 0; i < nb; ++i) {
            vector<vector<int>> d = bfs(buttons[i].first, buttons[i].second, maze);
            dd[i] = d;
            //从某个点到终点不需要拿石头
            dist[i][nb+1] = d[tx][ty];
        }

        for (int i = 0; i < nb; ++i) {
            int tmp = -1;
            for (int k = 0; k < ns; ++k) {
                int mid_x = stones[k].first, mid_y = stones[k].second;
                if (dd[i][mid_x][mid_y] != -1 && start_dist[mid_x][mid_y] != -1) {
                    if (tmp == -1 || tmp > dd[i][mid_x][mid_y] + start_dist[mid_x][mid_y]) {
                        tmp = dd[i][mid_x][mid_y] + start_dist[mid_x][mid_y];
                    }
                }
            }
            dist[i][nb] = tmp;
            for (int j = i + 1; j < nb; ++j) {
                int mn = -1;
                for (int k = 0; k < ns; ++k) {
                    int mid_x = stones[k].first, mid_y = stones[k].second;
                    if (dd[i][mid_x][mid_y] != -1 && dd[j][mid_x][mid_y] != -1) {
                        if (mn == -1 || mn > dd[i][mid_x][mid_y] + dd[j][mid_x][mid_y]) {
                            mn = dd[i][mid_x][mid_y] + dd[j][mid_x][mid_y];
                        }
                    }
                }
                dist[i][j] = mn;
                dist[j][i] = mn;
            }
        }

        //特判存在机关无法到达起点或者终点的情况,此时返回-1。
        for (int i = 0; i < nb; ++i) {
            if (dist[i][nb] == -1 || dist[i][nb+1] == -1) return -1;
        }

        //dp数组,-1代表没有遍历到
        //dp[mask][i]:当前已触发第i个机关,机关的触发状态为mask,此时的最小距离
        vector<vector<int>> dp(1 << nb, vector<int>(nb, -1));
        for (int i = 0; i < nb; ++i) {
            dp[1<<i][i] = dist[i][nb];
        }

        //由于更新的状态都比未更新的大,所以直接从小到大遍历即可
        for (int mask = 1; mask < (1 << nb); mask++) {
            for (int i = 0; i < nb; ++i) {
                //当前dp是合法的
                if (mask & (1 << i)) {
                    for (int j = 0; j < nb; ++j) {
                        //j不在mask里·
                        if (!(mask & (1 << j))) {
                            int next = mask | (1 << j);
                            if (dp[next][j] == -1 || dp[next][j] > dp[mask][i] + dist[i][j]) {
                                dp[next][j] = dp[mask][i] + dist[i][j];
                            }
                        }
                    }
                }
            }
        }

        int ret = -1;
        int final_mask = (1 << nb) - 1;
        for (int i = 0; i < nb; ++i) {
            if (ret == -1 || ret > dp[final_mask][i] + dist[i][nb+1]) {
                ret = dp[final_mask][i] + dist[i][nb+1];
            }
        }
        return ret;
    }
};

python3代码如下,

class Solution:
    def minimalSteps(self, grid: List[str]) -> int:
        n = len(grid)
        m = len(grid[0])
        snode = [-1,-1]
        enode = [-1,-1]
        stones = []
        buttons = []
        for i in range(n):
            for j in range(m):
                if grid[i][j] == 'S':
                    snode = [i,j]
                elif grid[i][j] == 'T':
                    enode = [i,j]
                elif grid[i][j] == 'M':
                    buttons.append([i,j])
                elif grid[i][j] == 'O':
                    stones.append([i,j])
        dirs = [[-1,0],[0,-1],[1,0],[0,1]]

        #检查snode,enode,stones,buttons
        #print(f"snode={snode},enode={enode},stones={stones},buttons={buttons}.")

        def get_dist(sx, sy) -> list:
            dist = [[-1] * m for _ in range(n)]
            q = collections.deque([])
            q.append([sx,sy])
            dist[sx][sy] = 0
            while len(q) > 0:
                i,j = q.popleft()
                for k in range(4):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if dist[ni][nj] != -1:
                        continue 
                    if grid[ni][nj] == '#':
                        continue 
                    dist[ni][nj] = dist[i][j] + 1
                    q.append([ni,nj])
            return dist 
        
        ns = len(stones)
        nb = len(buttons)

        dist_start = get_dist(snode[0], snode[1])

        if nb == 0: #特判机关数为0,直接返回起点到终点的距离
            return dist_start[enode[0]][enode[1]] 

        #存在起点到达不了机关buttons[i],返回-1
        for i in range(nb):
            x1,y1 = buttons[i] #第i个机关的坐标(x1,y1)
            if dist_start[x1][y1] == -1:
                return -1 

        dist_stone = []
        for x,y in stones:
            dist = get_dist(x,y)
            dist_stone.append(dist)
        
        #存在机关buttons[i]到达不了石块,返回-1
        for i in range(nb):
            x1,y1 = buttons[i] #第i个机关的坐标(x1,y1)
            flag = True 
            for j in range(ns):
                x2,y2 = stones[j] #第j个石块的坐标(x2,y2)
                if dist_stone[j][x1][y1] != -1: #第j个石块能够到达第i个机关
                    flag = False 
                    break 
            if flag:
                return -1 


        #计算起点-石块-第i个机关的最小距离d0[i]
        d0 = [-1] * nb 
        for i in range(nb):
            x1, y1 = buttons[i] #第i个机关的坐标(x1,y1)
            ans = -1
            for j in range(ns):
                x2,y2 = stones[j] #第j个石块的坐标(x2,y2)
                #dist_start[x2][y2] != -1
                #dist_stone[j][x1][y1] != -1
                if dist_start[x2][y2] == -1 or dist_stone[j][x1][y1] == -1:
                    continue 
                if ans == -1 or ans > dist_start[x2][y2] + dist_stone[j][x1][y1]:
                    ans = dist_start[x2][y2] + dist_stone[j][x1][y1]
            d0[i] = ans 
        
        #计算第i个机关-石块-第j个机关的最小距离d1[i][j]
        d1 = [[-1] * nb for _ in range(nb)]
        for i in range(nb):
            x1,y1 = buttons[i] #第i个机关的坐标(x1,y1)
            for j in range(i+1,nb):
                x2,y2 = buttons[j] #第j个机关的坐标(x2,y2)
                ans = -1
                for k in range(ns):
                    x3,y3 = stones[k] #第k个石块的坐标(x3,y3)
                    #dist_stone[k][x1][y1] != -1
                    #dist_stone[k][x2][y2] != -1
                    if dist_stone[k][x1][y1] == -1 or dist_stone[k][x2][y2] == -1:
                        continue 
                    if ans == -1 or ans > dist_stone[k][x1][y1] + dist_stone[k][x2][y2]:
                        ans = dist_stone[k][x1][y1] + dist_stone[k][x2][y2]
                d1[i][j] = d1[j][i] = ans 
        
        #计算第i个机关-终点的最小距离d2[i]
        dist_end = get_dist(enode[0],enode[1])
        d2 = [-1] * nb 
        for i in range(nb):
            x,y = buttons[i]
            d2[i] = dist_end[x][y] 
        
        #存在第i个机关到达不了终点
        for i in range(nb):
            if d2[i] == -1:
                return -1 
        
        #动态规划
        N = 1 << nb 
        dp = [[-1] * N for _ in range(nb)]
        #初始化
        for i in range(nb):
            mask = 1 << i 
            dp[i][mask] = d0[i]
        #状态转移
        for mask in range(0,N):
            for i in range(nb):
                if (mask >> i) & 1:
                    for j in range(nb):
                        if not (mask >> j) & 1:
                            next_mask = mask | (1 << j)
                            if d1[i][j] == -1:
                                continue 
                            if dp[j][next_mask] == -1 or dp[j][next_mask] > dp[i][mask] + d1[i][j]:
                                dp[j][next_mask] = dp[i][mask] + d1[i][j]
        
        #计算最终答案
        res = -1
        for i in range(nb):
            if dp[i][N-1] == -1 or d2[i] == -1:
                continue 
            if res == -1 or res > dp[i][N-1] + d2[i]:
                res = dp[i][N-1] + d2[i]
        return res 

题目49LCP 31. 变换的迷宫

解题思路:记忆化搜索。

C++代码如下,

class Solution {
public:
    

    bool escapeMaze(vector<vector<string>>& grid) {
        int T = grid.size();
        int n = grid[0].size();
        int m = grid[0][0].size();
        int dirs[5][2] = {{-1,0},{0,-1},{1,0},{0,1},{0,0}}; //可以原地等待

        int memo[55][55][105][5][5] = {false};
        function<bool(int,int,int,bool,bool)> dfs =[&] (int i, int j, int t, bool f1, bool f2) -> bool {
            if (memo[i][j][t][(int)f1][(int)f2]) return false; //已经访问过了,返回false
            memo[i][j][t][(int)f1][(int)f2] = true;
            bool res = false;
            if (i == n-1 && j == m-1) {
                return true;
            }
            if (t+1 >= T) {
                return false;
            }
            if (n-1-i + m-1-j > T-1-t) {
                return false;
            }
            for (int k = 0; k < 5; ++k) {
                int ni = i + dirs[k][0];
                int nj = j + dirs[k][1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
                if (grid[t+1][ni][nj] == '.') {
                    res = res || dfs(ni,nj,t+1,f1,f2);
                } else {
                    if (f1) {
                        res = res || dfs(ni,nj,t+1,false,f2);
                    } 
                    if (f2) {
                        for (int nt = t+1; nt < T; ++nt) {
                            res = res || dfs(ni,nj,nt,f1,false);
                        }
                    }
                }
            }
            return res;
        };
        return dfs(0,0,0,true,true);
    }
};

python3代码如下,

class Solution:
    def escapeMaze(self, grid: List[List[str]]) -> bool:
        T = len(grid)
        n = len(grid[0])
        m = len(grid[0][0])
        dirs = [[-1,0],[0,-1],[1,0],[0,1],[0,0]] #可以原地不动

        @cache
        def dfs(i, j, t, f1, f2) -> bool:
            #t时刻,我有卷轴f1和f2,从(i,j)出发,能到达终点
            res = False 
            if i == n-1 and j == m-1:
                return True 
            if t+1 >= T:
                return False 
            if n-1-i + m-1-j > T-1-t:
                return False #剪枝
            for k in range(5):
                ni = i + dirs[k][0]
                nj = j + dirs[k][1]
                if ni < 0 or ni >= n or nj < 0 or nj >= m:
                    continue 
                if grid[t+1][ni][nj] == '.':
                    res = res or dfs(ni,nj,t+1,f1,f2)
                else:
                    if f1: #卷轴1
                        res = res or dfs(ni,nj,t+1,False,f2)
                    if f2:
                        for nt in range(t+1,T):
                            res = res or dfs(ni,nj,nt,f1,False)
            return res 
        return dfs(0,0,0,True,True) 
#超时了
class Solution:
    def escapeMaze(self, grids: List[List[str]]) -> bool:
        step = len(grids)
        n = len(grids[0])
        m = len(grids[0][0]) 
        if n+m-1 > step: #if n+m-2 > step-1:
            return False #最小步数不满足要求
        
        dirs = [[-1,0],[0,-1],[1,0],[0,1],[0,0]] #可以原地不动
        visited = set()
        #grid[t][i][j]
        #grid[i][j][s]
        t = 0
        q = [(0,0,0,3,-1,-1)]
        visited.add((0,0,0,3,-1,-1))
        while t+1 < step:
            nq = []
            for tt,i,j,s,ti,tj in q:
                #当前时刻为t
                for k in range(5):
                    ni = i + dirs[k][0]
                    nj = j + dirs[k][1]
                    if ni < 0 or ni >= n or nj < 0 or nj >= m:
                        continue 
                    if grids[t+1][ni][nj] != '#' or (ni == ti and nj == tj): #没有必要使用卷轴
                        node = (tt+1, ni, nj, s, ti, tj)
                        if node not in visited:
                            if ni == n-1 and nj == m-1:
                                return True 
                            nq.append(node)
                            visited.add(node)
                    else: #需要使用卷轴
                        if s & 1: #可以使用卷轴1
                            node = (tt+1, ni, nj, s-1, ti, tj)
                            if node not in visited:
                                if ni == n-1 and nj == m-1:
                                    return True 
                                nq.append(node)
                                visited.add(node)
                        if s & 2: #可以使用卷轴2
                            node = (tt+1, ni, nj, s-2, ni, nj)
                            if node not in visited:
                                if ni == n-1 and nj == m-1:
                                    return True 
                                nq.append(node)
                                visited.add(node) 
            q = nq #更新q
            t += 1
        
        return False 

3 参考

灵神力扣题单