【算法学习计划】回溯 -- Floodfill

目录

733.图像渲染

200.岛屿数量

695.岛屿的最大面积

130.被围绕的区域

417.太平洋大西洋水流问题

529.扫雷游戏

LCR 130.衣橱整理

结语


扫描二维码关注公众号,回复: 17763959 查看本文章

今天我们来讲一讲回溯中一个典型的专题 -- floodfill,也就是洪水灌溉问题

今天我们将用 7 道题目来带各位深度了解一下这个算法

(下文中的标题都是leedcode对应题目的链接)

733.图像渲染

题意很简单,就是给你一个初始坐标,将所有和这个坐标对应值相同的位置全部改成另一个值而已

在正式开始做题目之前,我们需要先开辟两个数组表示上下左右的移动:

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

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

最后我们就直接从这个位置开始,分别对上下左右进行递归即可

不过需要注意的是,我们还有递归到原来位置的情况进而变成死递归,所以我们还需要一个check数组(bool类型),用来标记某个位置是否已经遍历过了

最后就是每遍历到一个位置,就将这个位置的值改变成color即可

带吗如下:

class Solution {
public:
    int col, n, m, cmp;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};

    void dfs(vector<vector<int>>& image, int i, int j)
    {
        image[i][j] = col;
        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < n && y >= 0 && y < m && image[x][y] == cmp)
                dfs(image, x, y);
        }
    }
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) 
    {
        col = color, n = image.size(), m = image[0].size(), cmp = image[sr][sc];
        if(image[sr][sc] == color) return image;
        dfs(image, sr, sc);
        return image;
    }
};

200.岛屿数量

这道题目其实也是相当简单的,本质还是进入二维数组里面,上下左右地递归

但是现在要我们计算岛屿的数量,这样,我们可以在外面创建一个 bool 类型的 check 数组,接着我们就只需要在主函数遍历这个 grid 数组,当我们遇到 1 的时候,就代表我们碰到岛屿了,我们就进入递归的逻辑,如果遇到 0,就直接跳过

但是我们进入岛屿递归逻辑之后,我们还需要将对应位置的check数组打上标记,代表这个地方我们已经遍历过了,这一整块岛屿我们找过一遍了

所以主函数里面就是,当我们碰到 1 并且这个位置在 check 数组里面是false也就是没遍历过的话,我们就进入递归逻辑,而其他情况就都跳过即可

最后我们每进入一次递归逻辑,外面计数器就++一次,最后返回计数器即可

代码如下:

class Solution {
public:
    int n, m;
    bool check[301][301];
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};

    void dfs(vector<vector<char>>& grid, int i, int j)
    {
        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < n && y >= 0 && y < m && grid[x][y] == '1' && !check[x][y])
            {
                check[x][y] = true;
                dfs(grid, x, y);
            }
        }
    }

    int numIslands(vector<vector<char>>& grid) 
    {
        int ret = 0;
        n = grid.size(), m = grid[0].size();
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
            {
                if(grid[i][j] == '1' && !check[i][j])
                {
                    ret++;
                    check[i][j] = true;
                    dfs(grid, i, j);
                }
            }
        return ret;
    }
};

695.岛屿的最大面积

由于是求最大面积,所以我们需要在全局位置创建一个变量 ret,用来判断最大的面积(也就是每遍历完一个面积,我们就和 ret 比较一下)

最后就是,我们求的是一整块,不是一条道走到尾的情况,如下:

这一块的最右边那一列,我们可以往上走,也可以往下走,但我们是需要将这些全部加起来的,所以我们正确的作坊应该是,全局创建一个变量计算面积,每遍历到一个地方我们就++这个变量即可,最后和 ret 比较

最后我们在进入下一次的递归逻辑之前,还需要将这个变量重新置为 1,因为进入递归的地方也要算一个面积

代码如下:

class Solution {
public:
    int n, m, ret, ps;
    bool check[51][51];
    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 = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < n && y >= 0 && y < m && grid[x][y] == 1 && !check[x][y])
            {
                ps++;
                check[x][y] = true;
                dfs(grid, x, y);
            }
        }
    }

    int maxAreaOfIsland(vector<vector<int>>& grid) 
    {
        ret = 0, ps = 0;
        n = grid.size(), m = grid[0].size();
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
            {
                if(grid[i][j] == 1 && !check[i][j])
                {
                    ps = 1;
                    check[i][j] = true;
                    dfs(grid, i, j);
                    ret = max(ps, ret);
                }
            }
        return ret;    
    }
};

130.被围绕的区域

这道题目,就是换一个思路的事情,因为求的是被包围的区域,所以我们可以先将那些没有被包围的区域打上标记,也就是从上下左右四条边开始出发,接着就是递归

最后那些没有递归到的位置,要么是海水要么是被包围的区域,我们就直接for循环嵌套遍历整张表,遇到 1 就改为 0 即可

代码如下:
 

class Solution {
public:
    int n, m;
    bool check[201][201];
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};

    void dfs(vector<vector<char>>& board, int i, int j)
    {
        check[i][j] = true;
        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < n && y >= 0 && y < m && board[x][y] == 'O' && !check[x][y])
                dfs(board, x, y);
        }
    }

    void solve(vector<vector<char>>& board) 
    {
        n = board.size(), m = board[0].size();
        for(int i = 0; i < n; i++)
        {
            if(board[i][0] == 'O' && !check[i][0])
                dfs(board, i, 0);
            if(board[i][m-1] == 'O' && !check[i][m-1])
                dfs(board, i, m-1);
        }    
        for(int j = 0; j < m; j++)
        {
            if(board[0][j] == 'O' && !check[0][j])
                dfs(board, 0, j);
            if(board[n-1][j] == 'O' && !check[n-1][j])
                dfs(board, n-1, j);
        }

        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                if(board[i][j] == 'O' && !check[i][j])
                    board[i][j] = 'X';
    }
};

417.太平洋大西洋水流问题

简单翻译一下题目,就是如果当前格子旁边有格子比他小或者等于的话,那么我们就可以走向下一个位置

然后题目要我们找出能够同时走到大西洋和太平洋的格子

其实这道题目正着解的话是很困难的,所以我们就需要正难则反,也就是,我们从上和左,以及左和下出发,就不断递归,然后我们设置两个数组,标记我们从大西洋或者太平洋出发能够走到的所有位置

最后我们直接遍历整个表格,如果有一个格子,从太平洋出发和大西洋出发都能走到的话,那就意味着这个点就是我们要找的点,直接记录当前节点即可

最后我们返回所有的符合条件的节点,代码如下:
 

class Solution {
public:
    int n, m;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    bool pa[201][201], at[201][201];

    void dfs(vector<vector<int>>& heights, int i, int j, bool flag)
    {
        if(flag) pa[i][j] = true;
        else at[i][j] = true;

        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < n && y >= 0 && y < m && heights[x][y] >= heights[i][j])
            {
                if(flag && !pa[x][y])
                    dfs(heights, x, y, flag);
                else if(!flag && !at[x][y])
                    dfs(heights, x, y, flag);
            }
        }
    }

    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) 
    {
        vector<vector<int>> ret;
        n = heights.size(), m = heights[0].size();
        // pa -- true
        // at -- false
        for(int i = 0; i < n; i++)
        {
            dfs(heights, i, 0, true);
            dfs(heights, i, m-1, false);
        }
        for(int j = 0; j < m; j++)
        {
            dfs(heights, 0, j, true);
            dfs(heights, n-1, j, false);
        }
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
            {
                if(pa[i][j] == true && at[i][j] == true)
                    ret.push_back({i, j});
            }
        return ret;
    }
};

529.扫雷游戏

其实也是给你一个起点,然后我们从这个起点开始递归展开

但是我们还需要判断一下特殊情况,也就是,如果这个位置直接就是雷,那么我们改完当前位置之后直接返回即可

其他的情况我们就直接进入递归逻辑,进入之后我们还需要判断一下,我们当前位置四周是否有雷,也就是这个位置是空格还是数字

其实我们专门写一个函数,用来做判断四周有多少个雷的工作即可

如果是空格,那就是 0 个雷,我们就直接改完然后继续递归下去即可

如果有数字,那我们就只能改成数字,但是我们不能继续递归下去了,游戏规则就是这样的

代码如下:

class Solution {
public:
    int n, m;
    bool check[51][51];
    int dx[8] = {0, 0, 1, -1, -1, 1, -1, 1};
    int dy[8] = {1, -1, 0, 0, -1, -1, 1, 1};

    char sta(vector<vector<char>>& board, int i ,int j)
    {
        int ret = 0;
        for(int k = 0; k < 8; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < n && y >= 0 && y < m && board[x][y] == 'M') ret++;
        }
        return ret + '0';
    }

    void dfs(vector<vector<char>>& board, int i, int j)
    {
        check[i][j] = true;
        char set = sta(board, i, j);
        if(set == '0') board[i][j] = 'B';
        else
        {
            board[i][j] = set;
            return;
        }
        for(int k = 0; k < 8; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < n && y >= 0 && y < m && !check[x][y] &&  board[x][y] != 'M')
                dfs(board, x, y);
        }
    }

    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) 
    {
        n = board.size(), m = board[0].size();
        int i = click[0], j = click[1];
        if(board[i][j] == 'M') 
        {
            board[i][j] = 'X';
            return board;
        }
        dfs(board, i, j);
        return board;
    }
};

LCR 130.衣橱整理

这一道题力扣翻译有点问题,我们看下面这张图,是剑指offer原题

其实这道题目知道题意之后相当简单,就是,我们每遍历到一个位置,就将两个下标分析一下,看看分开后加起来有没有超过cnt即可

接着就是,我们每到一个地方,就将计数器++,接着在进行递归逻辑之前,我们都先看一看,下标是否可行,如果可以,就直接递归,不行就不进入递归逻辑

而我们判断下标的工作可以专门写一个小函数来实现,这样子代码稍微优雅一点

代码如下:

class Solution {
public:
    int m, n, tar, ret;
    int dx[2] = {0, 1};
    int dy[2] = {1, 0};
    bool check[101][101];

    bool comp(int i, int j)
    {
        int sum = 0;
        string si = to_string(i);
        string sj = to_string(j);
        for(auto e : si) sum += e - '0';
        for(auto x : sj) sum += x - '0';
        return sum <= tar;
    }

    void dfs(int i, int j)
    {
        check[i][j] = true;
        ret++;
        for(int k = 0; k < 2; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && !check[x][y])
            {
                if(comp(x, y))
                    dfs(x, y);
            }
        }
    }

    int wardrobeFinishing(int _m, int _n, int cnt) 
    {
        m = _m, n = _n, tar = cnt, ret = 0;
        dfs(0, 0);
        return ret;
    }
};

结语

这篇文章到这里就结束了!!~( ̄▽ ̄)~*

如果觉得对你有帮助的,可以多多关注一下喔