简体   繁体   中英

Algorithm to find the shortest path in a grid

Background:

The problem is from leetcode :

In an N by N square grid, each cell is either empty (0) or blocked (1).

A clear path from top-left to bottom-right has length k if and only if it is composed of cells C_1, C_2, ..., C_k such that:

  • Adjacent cells C_i and C_{i+1} are connected 8-directionally (ie., they are different and share an edge or corner)
  • C_1 is at location (0, 0) (ie. has value grid[0][0] )
  • C_k is at location (N-1, N-1) (ie. has value grid[N-1][N-1] )
  • If C_i is located at (r, c) , then grid[r][c] is empty (ie. grid[r][c] == 0 ).

Return the length of the shortest such clear path from top-left to bottom-right. If such a path does not exist, return -1.

Question:

I was quite certain that my algorithm was correct but for this test case:

[[0,1,0,0,0],[0,1,0,0,0],[0,0,0,0,1],[0,1,1,1,0],[0,1,0,0,0]]

I get 9, and the correct answer is 7. Is there something I am doing wrong in the code below?

Code:

class Solution {
public:
    std::vector<std::vector<int>> dirs = {{0,1},{1,0},{-1,0},{0,-1},{1,1},{-1,-1},{1,-1},{-1,1}};
    int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
        if(grid.empty())
            return 0;

        if(grid[0][0] == 1 || grid[grid.size()-1][grid.size()-1] == 1) 
            return -1;


        int m = grid.size(), n = grid[0].size();
        std::pair<int, int> start = {0,0};
        std::pair<int, int> end = {m-1, n-1};
        std::vector<std::vector<bool>> visited(m, std::vector<bool>(n, false));
        std::priority_queue<std::pair<int,int>> q;
        q.push(start);
        visited[start.first][start.second] = true;
        int count = 1;

        while(!q.empty())
        {
            auto cur = q.top();
            q.pop();

            if(cur.first == end.first && cur.second == end.second)
                return count;

            for(auto dir : dirs)
            {
                int x = cur.first, y = cur.second;

                if(isValid(grid, x + dir[0], y + dir[1]))
                    x += dir[0], y += dir[1];

                if(!visited[x][y])
                {
                    visited[x][y] = true;
                    q.push({x,y});
                }
            }
            count++;
        }
        return -1;
    }

    bool isValid(std::vector<std::vector<int>>& grid, int i, int j)
    {
        if(i < 0 || i >= grid.size() || j < 0 || j >= grid[i].size() || grid[i][j] != 0)
            return false;
        return true;
    }
};

This is not a problem for which you would use Dijkstra's algorithm. That algorithm is targetting weighted graphs, while the problem you are dealing with is unweighted . Moreover, the way you use a priority queue is wrong. A C++ priority queue will by default pop the element that is largest , but since you provide it coordinates, that means it will pop the element with the largest coordinates. This is obviously not what you need. In fact, you do not have anything to order nodes by, since this problem is about an unweighted graph.

Secondly, count is counting the total number of nodes you visit. That cannot be right, since you surely also visit nodes that are not on the shortest path that you eventually find.

This kind of problem is solved with a standard depth-first search. You can do it with two vectors (no need for stack, queue or deque, ...): the second vector gets populated with the unvisited neighbors of all the nodes in the first. Once that cycle is completed, you replace the first vector with the second, create a new second vector, and repeat... until you find the target node. The number of times you do this (outer) repetition corresponds to the length of the path.

Here is your shortestPathBinaryMatrix function with the necessary adaptations to make it work:

int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
    if(grid.empty())
        return 0;

    if(grid[0][0] == 1 || grid[grid.size()-1][grid.size()-1] == 1) 
        return -1;

    int m = grid.size(), n = grid[0].size();
    pair<int, int> start = {0,0};
    pair<int, int> end = {m-1, n-1};
    vector<vector<bool>> visited(m, vector<bool>(n, false));
    // no priority queue needed: the graph is not weighted
    vector<std::pair<int,int>> q;
    q.push_back(start);
    visited[start.first][start.second] = true;
    int count = 1;

    while(!q.empty())
    {
        // just iterate the vector and populate a new one
        vector<std::pair<int,int>> q2;
        for(auto const& cur: q) {
            if(cur.first == end.first && cur.second == end.second)
                return count;
            for(auto dir : dirs)
            {
                int x = cur.first, y = cur.second;

                if(isValid(grid, x + dir[0], y + dir[1]))
                    x += dir[0], y += dir[1];

                if(!visited[x][y])
                {
                    visited[x][y] = true;
                    q2.push_back({x,y});
                }
            }
        }
        count++;
        q = q2; // prepare for next iteration
    }
    return -1;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM