dfs刷题矩阵搜索问题

发布于:2025-03-24 ⋅ 阅读:(29) ⋅ 点赞:(0)

N皇后

题目链接
在这里插入图片描述

题解

1. 画出决策树
2. 全局变量:ret用来统计结果,path统计每次的路径,checkcol检查行有没有Q,checkdig1检查主对角线有没有Q,checkdig2检查副对角线有没有Q
3. 剪枝:使用哈希表的策略,每一行不需要检查,每次都递归每一行该放的不会出现攻击,主对角线斜率一样,利用y-x = b,每一条线上截距是相同的,如果出现相同的就表明放过了Q,y-x可能为负数所以加上了n,副对角线y + x = b,和主对角线类似
4. 回溯:将列,主对角线和副对角线变为false,path[row][col]恢复为点
5. 递归出口:行越界的时候填完矩阵

在这里插入图片描述
在这里插入图片描述

代码

class Solution 
{
public:
    // 检查列,主对角线和副对角线
    // 行每次都只放一个不用检查,不会出现攻击
    bool checkcol[10],checkdig1[20],checkdig2[20];
    vector<vector<string>> ret;
    vector<string> path;
    int n;
    vector<vector<string>> solveNQueens(int _n) 
    { 
        n = _n;
        path.resize(n);
        for(int i = 0;i < n;i++)
        {
          path[i].append(n,'.');
        }
        dfs(0);
        return ret;
    }

    void dfs(int row)
    {
        if(n == row)
        {
            ret.push_back(path);
            return;
        }

        for(int col = 0;col < n;col++)// 放置每一行
        {
            if(!checkcol[col] && !checkdig1[row + col] && !checkdig2[col-row+n])
            {
                path[row][col] = 'Q';
                checkcol[col] = checkdig1[row + col] = checkdig2[col-row+n] = true;
                dfs(row+1);
                // 恢复现场
                checkcol[col] = checkdig1[row + col] = checkdig2[col-row+n] = false;
                path[row][col] = '.';
            }
        }
    }
};

有效的数独

题目链接
在这里插入图片描述

题解

1. 算法原理:去遍历这个数独,如果遇到了数字就判断这个数字是否在这一行,这一列,这一个方格中,如果不在就标记为true,如果在就直接返回false,说明出现了第二次了
2. bool checkrow[i][nums]:用来检查这一行是否存在nums这个数
bool checkcol[j][nums]:用来检查这一列是否存在nums这个数
bool check[i/3][j/3][nums]:用来检查小方格中是否存在nums这个数
,除3是把9 * 9的矩阵分为0,1,2下标的3*3的小方格,刚好第一个小方格中0,1,2下标的数除3都是0,第二,三个小方格也是如此

在这里插入图片描述

代码

class Solution 
{
public: 
    bool checkrow[9][10];// 检查行
    bool checkcol[9][10];// 检查列
    bool check[3][3][10];// 检查小方格
    bool isValidSudoku(vector<vector<char>>& board) 
    {
        for(int row = 0;row < 9;row++)
        {
            for(int col = 0;col < 9;col++)
            {
                if(board[row][col] != '.')
                {
                    int nums = board[row][col] - '0';
                    if(checkrow[row][nums] || checkcol[col][nums] || check[row/3][col/3][nums])
                    return false;
                    checkrow[row][nums] = checkcol[col][nums] = check[row/3][col/3][nums] = true;
                }
            }
        }
        return true;
    }
};

独解数

题目链接
在这里插入图片描述

题解

1. 画出决策树
2. 全局变量:checkcol检查列是否存在相同的数,checkrow检查行是否存在相同的数,gird检查小方格是否存在相同的数
3. 剪枝:bool dfs(board),如果这一行最终有一个格子填不了了,就返回false,不往后填了,返回到上一层去填正确的数
4. 回溯:填了的数bool变为false,然后board恢复为点
5. 递归出口:函数走完填完所有的格子就结束了
6. 算法原理:这题主要考察三个返回,第一个返回,如果填完了dfs返回true了,就不需要填下一个i了,第二个返回,如果所有数都不满足,说明前面填错了,返回上一层重新填,第三个返回,如果所有有点的格子都填完了,并且没有错,就返回true

在这里插入图片描述

代码

class Solution 
{
public:
    bool checkrow[9][10];
    bool checkcol[9][10];
    bool gird[3][3][10];
    void solveSudoku(vector<vector<char>>& board) 
    {
        for(int i = 0;i < 9;i++)
        {
            for(int j = 0;j < 9;j++)
            {
                if(board[i][j] != '.')
                {
                    int nums = board[i][j] - '0';
                    checkcol[j][nums] =  checkrow[i][nums] = gird[i/3][j/3][nums] = true;
                }
            }
        }
        dfs(board);
    }
    
    // 可以遍历所有空的位置的不需要row传参过来
    bool dfs(vector<vector<char>>& nums)
    {
        for(int row = 0;row < 9;row++)
        {
            for(int col = 0;col < 9;col++)
            {
               if(nums[row][col] == '.')
                {
                    for(char i = '1';i <= '9';i++)
                    {
                        int data = i - '0';
                        if(!checkcol[col][data] && !checkrow[row][data] && !gird[row/3][col/3][data])
                        {
                            nums[row][col] = i;
                            checkcol[col][data] = checkrow[row][data] = gird[row/3][col/3][data] = true;                       
                            if(dfs(nums) == true) return true;// 如果这个位置填完了返回了true,就不要再试下一个i了
                            // 恢复现场
                            nums[row][col] = '.';
                            checkcol[col][data] = checkrow[row][data] = gird[row/3][col/3][data] = false;
                        }
                    }
                    // 可能出现填不了的情况,1到9都不行
                    return false;
                }
            }
        }
        // 空位置全都填完了,返回true
        return true;
    }
};

单词搜索

题目链接
在这里插入图片描述

题解

1. 画出决策树
2. 全局变量:m,n矩阵的大小,vis数组标记这个格子已经走过了
3. 剪枝:如果找不到第一个匹配的字符就剪枝
4. 回溯:如果找不到下一个匹配的字符就回溯
5. 递归出口:pos是字符串的长度,如果匹配到越界就说明找到了
6. 函数头:i,j是匹配到第一个字符的坐标,s是字符串,pos是匹配的字符的位置,board是矩阵
7. 利用向量找i,j的上下左右,就不需要用四个for循环了

在这里插入图片描述在这里插入图片描述

代码

class Solution 
{
public:
    int m,n;
    // 标记该点是否使用过了
    bool vis[7][7];
    bool exist(vector<vector<char>>& board, string word) 
    {
        m = board.size();
        n = board[0].size();
        
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(board[i][j] == word[0])
                {
                    vis[i][j] = true;
                    if(dfs(board,i,j,word,1)) return true;
                    vis[i][j] = false;
                }
            }
        }
        // 都没找到匹配的字符串
        return false;
    }
    
    // 上下左右四个坐标
    int dx[4] = {0,0,-1,1};
    int dy[4] = {1,-1,0,0};

    bool dfs(vector<vector<char>>& board,int i,int j,string& word,int pos)
    {
        if(pos == word.size()) return true;

        for(int k = 0;k < 4;k++)
        {
            int x = i + dx[k],y = j + dy[k];
            if(x >= 0 && x < m && y >= 0&&y < n && !vis[x][y] && board[x][y] == word[pos])
            {
                vis[x][y] = true;
                if(dfs(board,x,y,word,pos+1)) return true;
                vis[x][y] = false;
            }
        }
        // 说明i,j位置找不到,是错误的位置
        return false;
    }
};

黄金矿工

题目链接
在这里插入图片描述

题解

1. 跟上一题非常类似,唯一要注意的是递归出口是可以每次更新的,因为写递归出口需要判断用for循环每次去更新比较麻烦
2. 可以使用path在参数中,dfs(path + gird[x][y]),这样每次加就不需要手动恢复现场了,函数会帮我们自动恢复现场,每次更新结果在dfs的开头,虽然多更新几次,但是不太麻烦
3. 我写的是全局的变量,需要恢复现场

在这里插入图片描述在这里插入图片描述

代码

class Solution 
{
public:
    bool vis[16][16];
    int m,n;
    int ret = 0;
    int ans;
    int getMaximumGold(vector<vector<int>>& grid) 
    {
        m = grid.size(),n = grid[0].size();
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(grid[i][j] != 0)
                {
                    // 避免重复使用这个位置
                    vis[i][j] = true;
                    ans += grid[i][j];
                    dfs(grid,i,j);
                    ret = max(ans,ret);
                    vis[i][j] = false;
                    ans -= grid[i][j];
                }
            }
        }
        return ret;
    }

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

    void dfs(vector<vector<int>>& grid,int i,int j)
    {
        for(int k = 0;k < 4;k++)
        {
            int x = dx[k] + i,y = dy[k] + j;
            if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y]!= 0)
            {
                vis[x][y] = true;
                ans += grid[x][y];
                dfs(grid,x,y);
                ret = max(ans,ret);
                vis[x][y] = false;
                ans -= grid[x][y];
            }
        }
    }
};

不同路径

题目链接
在这里插入图片描述

题解

1. 这题要注意的一点是设计一个全局变量count,用来统计0的个数,在参数中多一个step,如果step和count相同,并且这个点的值是2,那么就是一个路径
2. 这题和上题也非常相似

在这里插入图片描述

代码

class Solution 
{
public:
    // 统计0的个数
    int count;
    bool vis[21][21];
    int m,n;
    int ans;
    int uniquePathsIII(vector<vector<int>>& grid) 
    {
        // 初始化为局部变量了,符了
        m = grid.size(),n = grid[0].size();
        int dx,dy;
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(grid[i][j] == 0) count++;
                else if(grid[i][j] == 1)
                {
                    dx = i;
                    dy = j; 
                }
            }
        }    
        
        grid[dx][dy] = true;
        dfs(grid,dx,dy,0);
        return ans;
    }

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

    void dfs(vector<vector<int>>& grid,int i,int j,int step)
    {
        if(count == step && grid[i][j] == 2)
        {
            ans++;
            return;
        }

        for(int k = 0;k < 4;k++)
        {
            int x = dx[k] + i,y = dy[k] + j;
            if(x >= 0 && x < m && y >= 0&& y < n && !vis[x][y] && grid[x][y] == 0)
            {
                vis[x][y] = true;
                dfs(grid,x,y,step + 1);
                vis[x][y] = false;
            }
            if(count == step&&x >= 0 && x < m && y >= 0&& y < n && !vis[x][y] && grid[x][y] == 2)
            {
                vis[x][y] = true;
                dfs(grid,x,y,step);
                vis[x][y] = false;
            }
        }
    }
};

总结

1. 题目识别什么时候用矩阵搜索?
当出现向上,向下,向左,向右搜索时可以使用
2. 怎么使用矩阵搜索?
上面的三题里做了,就是模版
3. 如果是矩阵填空的题怎么做,例如N皇后?

可以用bool数组标记填过的行列斜对角线检查是否有效,然后一层一层去搜索,这种题主要也是画决策树,就是多了几个bool数组检查使用过的格子,之后不再使用这个格子


网站公告

今日签到

点亮在社区的每一天
去签到