LeetCode 1391. 檢查網格中是否存在有效路徑 (DFS+並查集)

題目(圖的靜態連通性)

給你一個 m x n 的網格 grid。網格里的每個單元都代表一條街道。grid[i][j] 的街道可以是:

1 表示連接左單元格和右單元格的街道。
2 表示連接上單元格和下單元格的街道。
3 表示連接左單元格和下單元格的街道。
4 表示連接右單元格和下單元格的街道。
5 表示連接左單元格和上單元格的街道。
6 表示連接右單元格和上單元格的街道。
在這裏插入圖片描述
你最開始從左上角的單元格 (0,0) 開始出發,網格中的「有效路徑」是指從左上方的單元格 (0,0) 開始、一直到右下方的 (m-1,n-1) 結束的路徑。該路徑必須只沿着街道走。

注意:你 不能 變更街道。

如果網格中存在有效的路徑,則返回 true,否則返回 false 。

題解

最樸實的DFS做法

對於每一種不同的街道,它的轉向是確定的,轉完之後能夠匹配的街道也是確定的,挨個枚舉就好了。
注意防止左右往返的情況,所以一定要用一個visited[][]數組來記錄一下,防止搜索冗餘。

bool visited[310][310];
class Solution {
public:
    int m,n;
    vector<vector<int>> grid;
    
    bool hasValidPath(vector<vector<int>>& grid) {
        this->grid = grid;
        memset(visited, 0, sizeof(visited));
        m = (int)grid.size();
        n = (int)grid[0].size();
        visited[0][0] = true;
        return dfs(0, 0);
    }
    
    bool dfs(int i,int j){
        if(i==m-1 && j==n-1){
            return true;
        }
        int node = grid[i][j];

        
        switch (node) {
            case 1:
            {
                int x = i,y = j-1;
                if(inArea(x, y)&& toLeft(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
            {
                int x = i,y = j+1;
                if(inArea(x, y)&& toRight(grid[x][y])&& !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
                break;
                
            case 2:
            {
                int x = i-1,y = j;
                if(inArea(x, y)&& toUp(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
            {
                int x = i+1,y = j;
                if(inArea(x, y)&& toDown(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
                break;
                
            case 3:
            {
                int x = i+1,y = j;
                if(inArea(x, y)&& toDown(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
            {
                int x = i,y = j-1;
                if(inArea(x, y)&& toLeft(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
                break;
                
            case 4:
            {
                int x = i+1,y = j;
                if(inArea(x, y)&& toDown(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
            {
                int x = i,y = j+1;
                if(inArea(x, y)&& toRight(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
                break;
                
            case 5:
            {
                int x = i,y = j-1;
                if(inArea(x, y)&& toLeft(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
            {
                int x = i-1,y = j;
                if(inArea(x, y)&& toUp(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
                break;
                
            case 6:
            {
                int x = i,y = j+1;
                if(inArea(x, y)&& toRight(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
            {
                int x = i-1,y = j;
                if(inArea(x, y)&& toUp(grid[x][y]) && !visited[x][y]){
                    visited[x][y] = true;
                    if(dfs(x, y)){return true;}
                    visited[x][y] = false;
                }
            }
                break;
                
            default:
                break;
        }
        return false;
    }
    
    bool inArea(int i,int j){
        return i>=0 && i<m && j>=0 && j<n;
    }
    
    bool toLeft(int v){
        return v==1 || v==4 || v==6;
    }
    
    bool toRight(int v){
        return v==1 || v==3 || v==5;
    }
    
    bool toUp(int v){
        return v==2 || v==3 || v==4;
    }
    
    bool toDown(int v){
        return v==2 || v==5 || v==6;
    }
};

比較精簡的寫法

(我自己寫的太囉嗦了)
大概思想,方向數組按順序寫,比如上右下左,編號爲0,1, 2, 3;每種街道的轉向都是確定的,都只有兩種,也用一個數組記錄一下。
最後,根據每種街道類型的不同,依次選擇兩種方向,得出下一步,但是要判斷是否可連接。
+2模4就相當於旋轉180度,一種很實用的技巧。

(go[G[x][y]][i]+2)%4 ==go[G[nx][ny]][0] || 
(go[G[x][y]][i]+2)%4==go[G[nx][ny]][1] 
// 上(0) 右(1) 下(2) 左(3)
const int dx[] = {-1,0,1,0};
const int dy[] = {0,1,0,-1};
//分別對應 6 種街道能轉的2種方向,對應編號 0 1 2 3 
const int go[][2] = {{},{1,3},{0,2},{2,3},{1,2},{0,3},{0,1}};


class Solution {
public:
    int m,n;
    vector<vector<int>> G;
    bool visited[310][310] = {0};

    bool hasValidPath(vector<vector<int>>& grid) {
         G = grid;
         m = grid.size();
         n = grid[0].size();
         memset(visited,0,sizeof(visited));
         return dfs(0,0);
    }

    bool dfs(int x,int y){
        visited[x][y] = 1;
        if(x==m-1&&y==n-1){
            return true;
        }
        for(int i=0;i<2;i++){
            int nx = x + dx[go[G[x][y]][i]];
            int ny = y + dy[go[G[x][y]][i]];
            if( nx>=0&&nx<m&&ny>=0&&ny<n&& !visited[nx][ny] &&
            ((go[G[x][y]][i]+2)%4 ==go[G[nx][ny]][0] || (go[G[x][y]][i]+2)%4==go[G[nx][ny]][1] ) ){
                if(dfs(nx,ny)){return true;}
                visited[nx][ny] = 0;
            }
        }
        return false;
    }
};

建圖+FloodFill

基本原理:
在這裏插入圖片描述
然後就是一個圖的連通性的DFS即可。

const int dx[] = {-1,0,1,0};
const int dy[] = {0,1,0,-1};
class Solution {
public:
    int G[1000][1000],m,n;
    vector<vector<int>> grid;
    bool hasValidPath(vector<vector<int>>& grid) {
        this->grid = grid;
        m = grid.size();
        n = grid[0].size();
        memset(G,0,sizeof(G));
        fill(G);

        dfs(1,1);
        return !G[3*m-2][3*n-2];
    }

    void dfs(int x,int y){
        G[x][y] = 0;
        for(int i=0;i<4;i++){
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(nx>=0&&ny>=0&&nx<3*m&&ny<3*n && G[nx][ny]){
                dfs(nx,ny);
            }
        }
    }

    void fill( int(*G)[1000] ){
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                int v = grid[i][j];
                G[3*i+1][3*j+1]=1;
                if(v==1){G[3*i+1][3*j]=G[3*i+1][3*j+2]=1;}
                if(v==2){G[3*i][3*j+1]=G[3*i+2][3*j+1]=1;}
                if(v==3){G[3*i+1][3*j]=G[3*i+2][3*j+1]=1;}
                if(v==4){G[3*i+2][3*j+1]=G[3*i+1][3*j+2]=1;}
                if(v==5){G[3*i+1][3*j]=G[3*i][3*j+1]=1;}
                if(v==6){G[3*i][3*j+1]=G[3*i+1][3*j+2]=1;}
            }
        }
    }
};

並查集維護圖的連通性

首先並查集維護連通性的時候,是通過merge(int x,int y)的方式,這就需要把圖中的每一個網格點映射爲一個唯一的整數。
這樣做即可。

    int getID(int x,int y){
        return x*n+y;
    }

那如何判斷,某個street可以和另一個street合併呢?
可以仿照一開始的做法。
比如說,1號街道如果想和它左邊的街道合併,左邊的街道必須是(1 、3、5);如果1號街道想和它右邊的街道合併,右邊的街道只能是(1、4、6)。
其他街道一樣處理。

最後要判斷兩個點的連通與否,只要看它們的代表元是否相同即可。

const int dx[] = {-1,0,1,0};
const int dy[] = {0,1,0,-1};


struct UnionFindSet{
    int f[300*300+10];
    UnionFindSet(){
        for(int i=0;i<300*300;i++){
            f[i] = i;
        }
    }
    
    void merge(int x,int y){
        f[find(x)] = find(y);
    }
    
    int find(int x){
        return x==f[x]?x:f[x]=find(f[x]);
    }
    
};


class Solution {
public:
    vector<vector<int>> grid;
    int m,n;
    UnionFindSet ufs;
    
    int getID(int x,int y){
        return x*n+y;
    }
    
    bool inArea(int x,int y){
        return x>=0&&x<m&&y>=0&&y<n;
    }
    
    void mergeL(int x,int y){
        int nx = x,ny = y-1;
        if(inArea(nx, ny)){
            int v = grid[nx][ny];
            if(v==1 || v==4 || v==6){
                ufs.merge(getID(x, y), getID(nx, ny));
            }
        }
    }
    
    void mergeR(int x,int y){
        int nx = x,ny = y+1;
        if(inArea(nx, ny)){
            int v = grid[nx][ny];
            if(v==1 || v==3 || v==5){
                ufs.merge(getID(x, y), getID(nx, ny));
            }
        }
    }
    
    void mergeU(int x,int y){
        int nx = x-1,ny = y;
        if(inArea(nx, ny)){
            int v = grid[nx][ny];
            if(v==2 || v==3 || v==4){
                ufs.merge(getID(x, y), getID(nx, ny));
            }
        }
    }
    
    void mergeD(int x,int y){
        int nx = x+1,ny = y;
        if(inArea(nx, ny)){
            int v = grid[nx][ny];
            if(v==2 || v==5 || v==6){
                ufs.merge(getID(x, y), getID(nx, ny));
            }
        }
    }
    
    
    bool hasValidPath(vector<vector<int>>& grid) {
        this->grid = grid;
        m = (int)grid.size();
        n = (int)grid[0].size();
        
        for(int x=0;x<m;x++){
            for(int y=0;y<n;y++){
                int kind = grid[x][y];
                switch (kind) {
                    case 1:
                        mergeL(x, y);mergeR(x, y);
                        break;
                        
                    case 2:
                        mergeU(x, y);mergeD(x, y);
                        break;
                        
                    case 3:
                        mergeL(x, y);mergeD(x, y);
                        break;
                        
                    case 4:
                        mergeR(x, y);mergeD(x, y);
                        break;
                        
                    case 5:
                        mergeL(x, y);mergeU(x, y);
                        break;
                        
                    case 6:
                        mergeR(x, y);mergeU(x, y);
                        break;
                    default:
                        break;
                }
            }
        }
        return ufs.find(getID(0, 0))==ufs.find(getID(m-1, n-1));
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章