FloodFill 算法实现排雷等

题目一:被围绕的区域(中等)

130. 被围绕的区域 - 力扣(LeetCode)

给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

示例 1:

输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

示例 2:

输入:board = [["X"]]
输出:[["X"]]

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j] 为 'X' 或 'O'

题目分析

思路一

因为我们不确定哪一块的区域与边缘的'O'相邻,那么,我们可以使用一个和board相同的数组去尝试,如果记录的标记结果最后表示这一块区域与边缘'O'相邻,那就用board重置尝试数组,否则将尝试的数组赋值给board。

class Solution 
{
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    int m,n;
    vector<vector<bool>> isused;
    vector<vector<char>> copyboard;//尝试数组
    bool isside;//进行标记,表示该区域是否与边缘'O'相邻。
public:
    void solve(vector<vector<char>>& board) 
    {
        m=board.size(),n=board[0].size();
        isused=vector<vector<bool>>(m,vector<bool>(n,false));
        copyboard=board;

        for(size_t i=0;i<m;i++)
        {
            for(size_t j=0;j<n;j++)
            {
                if(isused[i][j]==false&&board[i][j]=='O')
                {
                    isside=false;//每进入一块区域时,需要重置标记
                    solvedfs(i,j);
                    if(isside==true)//表示相邻,说明board不能改变当前区域,重置copyboard
                    {
                        copyboard=board;
                    }
                    else//表示该区域被'X'完全包围,将copyboard赋值给board
                    {
                        board=copyboard;
                    }
                }
            }
        }
    }

    void solvedfs(int x,int y)
    {
        copyboard[x][y]='X';
        isused[x][y]=true;
        if(x==0||y==0||x==m-1||y==n-1)//记录是否与边缘'O'相邻
        {
            isside=true;
        }

        for(size_t i=0;i<4;i++)
        {
            int newx=x+dx[i];
            int newy=y+dy[i];
            if(newx>=0&&newx<m&&newy>=0&&newy<n&&isused[newx][newy]==false&&copyboard[newx][newy]=='O')
            {
                solvedfs(newx,newy);
            }
        }
    }
};

思路二

正难则反。
可以先利用dfs 将与边缘相连的 '0' 区域做上标记,然后重新遍历矩阵,将没有标记过的 '0' 修改成 'X' 即可。
class Solution 
{
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    int m,n;
    vector<vector<bool>> isused;
public:
    void solve(vector<vector<char>>& board) 
    {
        m=board.size(),n=board[0].size();
        isused=vector<vector<bool>>(m,vector<bool>(n,false));

        //对边缘的连接'O'区域标记为'K'
        for(size_t i=0;i<m;i++)
        {
            if(isused[i][0]==false&&board[i][0]=='O') solvedfs(board,i,0,'K');
            if(isused[i][n-1]==false&&board[i][n-1]=='O') solvedfs(board,i,n-1,'K');
        }

        for(size_t j=0;j<n;j++)
        {
            if(isused[0][j]==false&&board[0][j]=='O') solvedfs(board,0,j,'K');
            if(isused[m-1][j]==false&&board[m-1][j]=='O') solvedfs(board,m-1,j,'K');
        }
        
        //对全部被'X'包围的区域修改为'X'
        for(size_t i=0;i<m;i++)
        {
            for(size_t j=0;j<n;j++)
            {
                if(isused[i][j]==false&&board[i][j]=='O')
                {
                    solvedfs(board,i,j,'X');
                }
            }
        }

        //将标记区域进行还原
        for(size_t i=0;i<m;i++)
        {
            for(size_t j=0;j<n;j++)
            {
                if(board[i][j]=='K')
                {
                    board[i][j]='O';
                }
            }
        }
    }

    void solvedfs(vector<vector<char>>& board,int x,int y,char tmp)
    {
        board[x][y]=tmp;
        isused[x][y]=true;

        for(size_t i=0;i<4;i++)
        {
            int newx=x+dx[i];
            int newy=y+dy[i];
            if(newx>=0&&newx<m&&newy>=0&&newy<n&&isused[newx][newy]==false&&board[newx][newy]=='O')
            {
                solvedfs(board,newx,newy,tmp);
            }
        }
    }
};

题目二:太平洋大西洋水流问题(中等)

417. 太平洋大西洋水流问题 - 力扣(LeetCode)

有一个 m × n 的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。

这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度 。

岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。

返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋 。

示例 1:

输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]

示例 2:

输入: heights = [[2,1],[1,2]]
输出: [[0,0],[0,1],[1,0],[1,1]]

提示:

  • m == heights.length
  • n == heights[r].length
  • 1 <= m, n <= 200
  • 0 <= heights[r][c] <= 105

题目分析

正难则反。如果直接去判断某⼀个位置是否既能到大西洋也能到太平洋,会重复遍历很多路径。
我们反着来,从大西洋沿岸开始反向 dfs ,这样就能找出那些点可以流向大西洋;同理,从太平洋沿岸也反向 dfs ,这样就能找出那些点可以流向太平洋。那么,被标记两次的点,就是我们要找的结果。
class Solution 
{
    vector<vector<int>> result;
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    vector<int> path;//记录下标的工具,方便将下标传入result。
    int m,n;
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) 
    {
        m=heights.size(),n=heights[0].size();
        path=vector<int>(2);
        vector<vector<bool>> recordpacific(m,vector<bool>(n,false));//表示该点的水可以流向pacific
        vector<vector<bool>> recordAtlantic(m,vector<bool>(n,false));//表示该点的水可以流向Atlantic

        dfs(heights,0,0,recordpacific,0,0);//对可以流向太平洋的点进行标记,
        dfs(heights,m-1,n-1,recordAtlantic,m-1,n-1);//对可以流向大西洋的点进行标记

        for(size_t i=0;i<m;i++)
        {
            for(size_t j=0;j<n;j++)
            {
                if(recordpacific[i][j]==true&&recordAtlantic[i][j]==true)
                {
                    path[0]=i;
                    path[1]=j;
                    result.push_back(path);
                }
            }
        }

        return result;
    }

    //x,y:表示的是所需查看的下标点,而boundaryx和boundaryy表示的是与对应海洋贴合的边界
    void dfs(vector<vector<int>>& heights,int x,int y,vector<vector<bool>>& record,int boundaryx,int boundaryy)
    {
        record[x][y]=true;

        for(size_t i=0;i<4;i++)
        {
            int newx=x+dx[i];
            int newy=y+dy[i];
            if(newx>=0&&newx<m&&newy>=0&&newy<n&&record[newx][newy]==false&&
            (heights[newx][newy]>=heights[x][y]||newx==boundaryx||newy==boundaryy))
            {
               dfs(heights,newx,newy,record,boundaryx,boundaryy);
            }
        }
    }
};

题目三:扫雷(中等)

529. 扫雷游戏 - 力扣(LeetCode)

让我们一起来玩扫雷游戏!

给你一个大小为 m x n 二维字符矩阵 board ,表示扫雷游戏的盘面,其中:

  • 'M' 代表一个 未挖出的 地雷,
  • 'E' 代表一个 未挖出的 空方块,
  • 'B' 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的 已挖出的 空白方块,
  • 数字'1' 到 '8')表示有多少地雷与这块 已挖出的 方块相邻,
  • 'X' 则表示一个 已挖出的 地雷。

给你一个整数数组 click ,其中 click = [clickr, clickc] 表示在所有 未挖出的 方块('M' 或者 'E')中的下一个点击位置(clickr 是行下标,clickc 是列下标)。

根据以下规则,返回相应位置被点击后对应的盘面:

  1. 如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X' 。
  2. 如果一个 没有相邻地雷 的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的 未挖出 方块都应该被递归地揭露。
  3. 如果一个 至少与一个地雷相邻 的空方块('E')被挖出,修改它为数字('1' 到 '8' ),表示相邻地雷的数量。
  4. 如果在此次点击中,若无更多方块可被揭露,则返回盘面。

示例 1:

输入:board = [["E","E","E","E","E"],["E","E","M","E","E"],["E","E","E","E","E"],["E","E","E","E","E"]], click = [3,0]
输出:[["B","1","E","1","B"],["B","1","M","1","B"],["B","1","1","1","B"],["B","B","B","B","B"]]

示例 2:

输入:board = [["B","1","E","1","B"],["B","1","M","1","B"],["B","1","1","1","B"],["B","B","B","B","B"]], click = [1,2]
输出:[["B","1","E","1","B"],["B","1","X","1","B"],["B","1","1","1","B"],["B","B","B","B","B"]]

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 50
  • board[i][j] 为 'M''E''B' 或数字 '1' 到 '8' 中的一个
  • click.length == 2
  • 0 <= clickr < m
  • 0 <= clickc < n
  • board[clickr][clickc] 为 'M' 或 'E'

题目分析

class Solution 
{
    //表示当前下标附近的八个位置的下标
    int dx[8]={0,0,1,-1,-1,1,1,-1};
    int dy[8]={1,-1,0,0,1,1,-1,-1};
    vector<vector<bool>> isused;
    int m,n;
public:
    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) 
    {
        if(board[click[0]][click[1]]=='M')
        {
            board[click[0]][click[1]]='X';
            return board;
        }

        m=board.size(),n=board[0].size();
        isused=vector<vector<bool>>(m,vector<bool>(n,false));
        updateBoarddfs(board,click[0],click[1]);
        return board;
    }

    void  updateBoarddfs(vector<vector<char>>& board,int x,int y)
    {
        //搜索查看当前的下标点附近的八个位置有几个地雷
        int count=0;
        for(size_t i=0;i<8;i++)
        {
            int newx=x+dx[i];
            int newy=y+dy[i];
            if(newx>=0&&newx<m&&newy>=0&&newy<n&&board[newx][newy]=='M')
            {
                count++;
            }
        }
        //如果存在地雷,那么就会将当前下标修改为附近地雷,同时可以设置返回条件,以防止对当前下标继续对旁边进行递归
        if(count)
        {
            board[x][y]='0'+count;
            return;
        }
        else
        {
            board[x][y]='B';
            for(size_t i=0;i<8;i++)
            {
                int newx=x+dx[i];
                int newy=y+dy[i];
                if(newx>=0&&newx<m&&newy>=0&&newy<n&&board[newx][newy]=='E')
                {
                    updateBoarddfs(board,newx,newy);
                }
            }
        }
    }
};

题目四:黄金矿工(中等)

1219. 黄金矿工 - 力扣(LeetCode)

你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0

为了使收益最大化,矿工需要按以下规则来开采黄金:

  • 每当矿工进入一个单元,就会收集该单元格中的所有黄金。
  • 矿工每次可以从当前位置向上下左右四个方向走。
  • 每个单元格只能被开采(进入)一次。
  • 不得开采(进入)黄金数目为 0 的单元格。
  • 矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。

示例 1:

输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
 [5,8,7],
 [0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7。

示例 2:

输入:grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]
输出:28
解释:
[[1,0,7],
 [2,0,6],
 [3,4,5],
 [0,3,0],
 [9,0,20]]
一种收集最多黄金的路线是:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7。

提示:

  • 1 <= grid.length, grid[i].length <= 15
  • 0 <= grid[i][j] <= 100
  • 最多 25 个单元格中有黄金。

题目分析

枚举矩阵中所有的位置当成起点,来一次深度优先遍历,统计出所有情况下能收集到的黄金数的最大值即可。
class Solution 
{
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    vector<vector<bool>> isused;
    int maxgold;
    int m,n;
public:
    int getMaximumGold(vector<vector<int>>& grid) 
    {
        m=grid.size(),n=grid[0].size();
        isused=vector<vector<bool>>(m,vector<bool>(n,false));

        for(size_t i=0;i<m;i++)
        {
            for(size_t j=0;j<n;j++)
            {
                if(grid[i][j]==0) continue;

                isused[i][j]=true;
                getMaximumGolddfs(grid,i,j,grid[i][j]);
                isused[i][j]=false;
            }
        }
        return maxgold;
    }
    //对于需要对递归的每一条路径都进行计数的递归函数,将计数作为参数传参可以确保正常计算,同时
    //可以避免需要自己恢复现场而可能出现的失误,因此用pathsum作为参数
    void getMaximumGolddfs(vector<vector<int>>& grid,int x,int y,int pathsum)
    {

        for(size_t i=0;i<4;i++)
        {
            int newx=x+dx[i];
            int newy=y+dy[i];
            if(newx>=0&&newx<m&&newy>=0&&newy<n&&isused[newx][newy]==false&&grid[newx][newy]!=0)
            {
                isused[newx][newy]=true;
                getMaximumGolddfs(grid,newx,newy,pathsum+grid[newx][newy]);
                isused[newx][newy]=false;
            }
        }
        maxgold=max(maxgold,pathsum);
    }
};

题目五:不同路径三(困难)

980. 不同路径 III - 力扣(LeetCode)

在二维网格 grid 上,有 4 种类型的方格:

  • 1 表示起始方格。且只有一个起始方格。
  • 2 表示结束方格,且只有一个结束方格。
  • 0 表示我们可以走过的空方格。
  • -1 表示我们无法跨越的障碍。

返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目

每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格

示例 1:

输入:[[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
输出:2
解释:我们有以下两条路径:
1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2)
2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)

示例 2:

输入:[[1,0,0,0],[0,0,0,0],[0,0,0,2]]
输出:4
解释:我们有以下四条路径: 
1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3)
2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3)
3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3)
4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)

示例 3:

输入:[[0,1],[2,0]]
输出:0
解释:
没有一条路能完全穿过每一个空的方格一次。
请注意,起始和结束方格可以位于网格中的任意位置。

提示:

  • 1 <= grid.length * grid[0].length <= 20

题目分析

读题我们可以发现完成该题目必须满足以下几个条件:

1、不能连接到-1的节点。

2、必须从数为1的节点开始。

3、终点的数必须是2.

4、除了-1的节点,其余所有的节点必须”一笔画“连接完。

读题结论

得出:(1)需要知道节点为1的下标,(2)需要知道除了-1以外一共有多少节点,设为step

           (3)深搜的递归结束条件是:遍历到节点为2时,递归结束。这时候分俩种情况:

(一)当恰好已经遍历了step个节点,该节点为2恰好是最后一个节点,说明该条路径正确

(二)还未遍历了step个节点,该路径错误。

   结果:递归结束。

class Solution 
{
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    vector<vector<bool>> isused;
    int m,n;
    int startx,starty;
    int step,count;
public:
    int uniquePathsIII(vector<vector<int>>& grid) 
    {
        m=grid.size(),n=grid[0].size();
        isused=vector<vector<bool>>(m,vector<bool>(n,false));
        //求出起始点下标,以及除了-1以外有多少节点
        for(size_t i=0;i<m;i++)
        {
            for(size_t j=0;j<n;j++)
            {
                if(grid[i][j]!=-1) step++;
                if(grid[i][j]==1) startx=i,starty=j;
            }
        }
        isused[startx][starty]=true;
        uniquePathsIIIdfs(grid,startx,starty,1);
        return count;
    }

    void uniquePathsIIIdfs(vector<vector<int>>& grid,int x,int y,int pathstep)
    {
        //递归结束条件
        if(grid[x][y]==2)
        {
            if(pathstep==step) count++;
            return;
        }

        for(size_t i=0;i<4;i++)
        {
            int newx=x+dx[i];
            int newy=y+dy[i];
            if(newx>=0&&newx<m&&newy>=0&&newy<n&&isused[newx][newy]==false&&grid[newx][newy]!=-1)
            {
                isused[newx][newy]=true;
                uniquePathsIIIdfs(grid,newx,newy,pathstep+1);
                isused[newx][newy]=false;
            }
        }
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值