FloodFill-----洪水灌溉算法(DFS例题详解)

目录

一.图像渲染:

代码详解:

二.岛屿数量:

代码详解:

三.岛屿的最大面积:

代码详解:

四.被围绕的区域:

代码详解:

五.太平洋大西洋水流问题:

代码详解:


FloodFill算法简介:FloodFill(泛洪填充)算法是一种图像处理的基本算法,用于填充连通区域。该算法通常从一个种子点开始,沿着种子点的相邻像素进行填充,直到遇到边界或者其他指定的条件为止。FloodFill 算法的主要应用是在图像编辑软件中实现填充操作,以及在计算机图形学、计算机视觉等领域中进行区域填充。

下面我们通过一些题目来理解这个算法思想:

一.图像渲染:

  • 题目链接:733. 图像渲染 - 力扣(LeetCode)
  • 题目描述:

有一幅以 m x n 的二维整数数组表示的图画 image ,其中 image[i][j] 表示该图画的像素值大小。

你也被给予三个整数 sr ,  sc 和 newColor 。你应该从像素 image[sr][sc] 开始对图像进行 上色填充 。

为了完成 上色工作 ,从初始像素开始,记录初始坐标的 上下左右四个方向上 像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应 四个方向上 像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为 newColor 。

最后返回 经过上色渲染后的图像 。 ​

  • 对应函数签名如下:

  •  思路:我们从给定的起点开始,进行深度优先搜索(上下左右四个方向)。每次搜索到一个方格时,如果其与初始位置的方格颜色相同,就将该方格的颜色更新,以防止重复搜索;如果不相同,则进行回溯。这里我们设置初始方格为target.

代码详解:

解法一:

class Solution {
    //记录走过的路径,防止走回头路
    boolean[][] used;
    int target;
    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        int m = image.length,n = image[0].length;
        used = new boolean[m][n];
        target = image[sr][sc];
        dfs(image,sr,sc,color);
        return image;
    }
    public void dfs(int[][] image,int i,int j,int color){
        int m = image.length,n = image[0].length;
        //剪枝,越界直接返回
        if(i < 0 || j < 0 || i >= m || j >= n){
            return ;
        }
        //使用过的位置也直接返回
        if(used[i][j]) return ;
      
        if(image[i][j] == target){
            //上下左右去深搜,符合条件的都标记为color
               image[i][j] = color;
               used[i][j] = true;
             dfs(image,i - 1,j,color);
             dfs(image,i + 1,j,color);
             dfs(image,i,j - 1,color);
             dfs(image,i,j + 1,color);
        }
       
    }
}

解法二:基于解法一,我们可以通过定义两个数组来表示方向:dx[ ],dy[ ],其中dx[ ],dy[ ]的位置要一一对应,具体操作如下:

 代码详解:

class Solution {
    boolean[][] used;
    int target;
    int[] dx = {-1,1,0,0};
    int[] dy = {0,0,1,-1};
    public int[][] floodFill(int[][] image, int sr, int sc, int color) {
        int m = image.length,n = image[0].length;
        used = new boolean[m][n];
        target = image[sr][sc];
        dfs(image,sr,sc,color);
        return image;
    }
    public void dfs(int[][] image,int i,int j,int color){
        int m = image.length,n = image[0].length;
        //每次进入都进行标记,并将该位置值改为color
        used[i][j] = true;
        image[i][j] = color;
        //相当于上下左右四个方向进行深搜
        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
            && !used[x][y] && image[x][y] == target){
                dfs(image,x,y,color);
            }
        }
    }
}

运行结果:

二.岛屿数量:

  • 题目链接:200. 岛屿数量 - 力扣(LeetCode)
  • 题目描述:

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

  •  对应函数签名如下:

  • 思路:
  • 遍历整个矩阵,每次找到「⼀块陆地」的时候:
  •  说明找到「⼀个岛屿」,记录到最终结果 res⾥⾯
  • 并且将这个陆地相连的所有陆地,也就是这块「岛屿」,全部「变成海洋」。这样的话,我们下次 遍历到这块岛屿的时候,它「已经是海洋」了,不会影响最终结果。
  •  其中「变成海洋」的操作,可以利⽤「深搜」来解决

代码详解:

 解法一:与上面一样,两种解法(类似):

class Solution {
    int res = 0;
    public int numIslands(char[][] grid) {
        int m = grid.length,n = grid[0].length;
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(grid[i][j] == '1'){
                   //每次找到一个岛屿记录一下,再将这个岛屿淹没
                    res++;
                    dfs(grid,i,j);
                }
            }
        }
        return res;
    }
    public void dfs(char[][] grid,int i,int j){
        int m = grid.length,n = grid[0].length;
        //处理边界情况
        if(i < 0 || j < 0 || i >= m || j >= n){
            return ;
        }
        if(grid[i][j] == '0') return ;
        grid[i][j] = '0'; 
        //上下左右去淹没这个岛屿
        dfs(grid,i - 1,j);
        dfs(grid,i + 1,j);
        dfs(grid,i,j - 1);
        dfs(grid,i,j + 1);
    }
}

解法二:

class Solution {
    int res = 0;
    int[] dx = {0,0,-1,1};
    int[] dy = {1,-1,0,0};
    public int numIslands(char[][] grid) {
        int m = grid.length,n = grid[0].length;
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(grid[i][j] == '1'){
                   //说明找到「⼀个岛屿」,记录到最终结果 res⾥⾯
                    res++;
                    dfs(grid,i,j);//将这个岛屿淹没
                }
            }
        }
        return res;
    }
    public void dfs(char[][] grid,int i,int j){
        int m = grid.length,n = grid[0].length;
       
        grid[i][j] = '0'; 
        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
            && grid[x][y] != '0'){
                dfs(grid,x,y);
            }
        }
    }
}

运行结果:

 

三.岛屿的最大面积:

  • 题目链接:695. 岛屿的最大面积 - 力扣(LeetCode)
  • 题目描述:

    给你一个大小为 m x n 的二进制矩阵 grid 。

    岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

    岛屿的面积是岛上值为 1 的单元格的数目。

    计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 

  • 对应函数签名:

算法思路:

• 遍历整个矩阵,每当遇到⼀块⼟地的时候,就⽤「深搜」或者「宽搜」将与这块⼟地相连的「整个 岛屿」的⾯积计算出来

• 然后在搜索得到的「所有的岛屿⾯积」求⼀个「最⼤值」即可

• 在搜索过程中,为了「防⽌搜到重复的⼟地」:

◦ 可以开⼀个同等规模的「布尔数组」,标记⼀下这个位置是否已经被访问过;

◦ 也可以将原始矩阵的 1 修改成 0 ,但是这样操作会修改原始矩阵。 

代码详解:

 解法一:

class Solution {
    int maxArea = 0;
    int count;
    boolean[][] used;
    public int maxAreaOfIsland(int[][] grid) {
        int m = grid.length,n = grid[0].length;
        used = new boolean[m][n];
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(grid[i][j] == 1){
                    //每次找到一个岛屿都要重置计数
                    count = 0;
                    dfs(grid,i,j);
                    maxArea = Math.max(maxArea,count);
                }
            }
        }
        return maxArea;
    }

    public void dfs(int[][] grid,int i,int j){
        int m = grid.length,n = grid[0].length;
        //处理边界情况
        if(i < 0 || j < 0 || i >= m || j >= n){
            return ;
        }
        if(grid[i][j] == 0) return ;
        if(used[i][j]) return ;

        used[i][j] = true;
        count++;
        dfs(grid,i - 1,j);
        dfs(grid,i + 1,j);
        dfs(grid,i,j - 1);
        dfs(grid,i,j + 1);
    }
}

解法二:

class Solution {
    int maxArea = 0;
    int count = 0;
    int[] dx = {0,0,-1,1};
    int[] dy = {1,-1,0,0};
    boolean[][] used;
    public int maxAreaOfIsland(int[][] grid) {
        int m = grid.length,n = grid[0].length;
        used = new boolean[m][n];
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(grid[i][j] == 1){
                    //每次找到一个岛屿都要重置计数
                    count = 0;
                    dfs(grid,i,j);
                    maxArea = Math.max(maxArea,count);
                }
            }
        }
        return maxArea;
    }

    public void dfs(int[][] grid,int i,int j){
        int m = grid.length,n = grid[0].length;

        used[i][j] = true;
        count++;
        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 
            && !used[x][y] && grid[x][y] != 0){
                dfs(grid,x,y);
            }
        }

    }
}

运行结果:

四.被围绕的区域:

  • 题目链接:130. 被围绕的区域 - 力扣(LeetCode)
  • 题目描述:给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

  • 对应函数签名:

  • 算法思路: 
  • 正难则反。 可以先利⽤ dfs 将与边缘相连的 '0' 区域做上标记,然后重新遍历矩阵,将没有标记过的 '0' 修改成 'X' 即可。

 

代码详解:

class Solution {
    boolean[][] used;
    public void solve(char[][] board) {
        int m = board.length,n = board[0].length;
        used = new boolean[m][n];
        //分别对应上下左右,标记外围的'O'
        for(int i = 0;i < n;i++){
            dfs2(board,0,i);
            dfs2(board,m - 1,i);
        }
        for(int j = 0;j < m;j++){
            dfs2(board,j,0);
            dfs2(board,j,n - 1);
        }

        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(board[i][j] != 'X' && !used[i][j]){
                    dfs(board,i,j);
                }
            }
        }
        
    }
    //将内部的'O'全部标记为'X'
     public void dfs(char[][] board,int i,int j){
        int m = board.length,n = board[0].length;
        if(i < 0 || j < 0 || i >= m || j >= n){
            return ;
        }
        if(board[i][j] == 'X') return;
        if(used[i][j]) return ;


        used[i][j] = true;

        board[i][j] = 'X';

        dfs(board,i - 1,j);
        dfs(board,i + 1,j);
        dfs(board,i,j - 1);
        dfs(board,i,j + 1);
    }
   //将外围的位置标记为true,后续不会对其进行操作
    public void dfs2(char[][] board,int i,int j){
        int m = board.length,n = board[0].length;
        if(i < 0 || j < 0 || i >= m || j >= n){
            return ;
        }
        if(board[i][j] == 'X') return;
        if(used[i][j]) return ;
        used[i][j] = true;
        dfs2(board,i - 1,j);
        dfs2(board,i + 1,j);
        dfs2(board,i,j - 1);
        dfs2(board,i,j + 1);
    }
}

运行结果:

五.太平洋大西洋水流问题:

  • 题目链接:417. 太平洋大西洋水流问题 - 力扣(LeetCode)
  • 题目描述:

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

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

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

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

  • 对应函数标签: 

  •  算法思路:

正难则反。 如果直接去判断某⼀个位置是否既能到⼤西洋也能到太平洋,会重复遍历很多路径。 我们反着来,从⼤西洋沿岸开始反向 dfs ,这样就能找出那些点可以流向⼤西洋;同理,从太平洋沿 岸也反向 dfs ,这样就能找出那些点可以流向太平洋。那么,被标记两次的点,就是我们要找的结果

 

代码详解:

class Solution {
    int m ,n;
    int[] dx = {0,0,1,-1};
    int[] dy = {1,-1,0,0};
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        m = heights.length;
        n = heights[0].length;
        boolean[][] pac = new boolean[m][n];
        boolean[][] atl = new boolean[m][n];

        //先搞太平洋
        for(int j = 0;j < n;j++) dfs(heights,0,j,pac);
        for(int i = 0;i < m;i++) dfs(heights,i,0,pac);

        //在搞大西洋
        for(int i = 0;i < m;i++) dfs(heights,i,n - 1,atl);
        for(int j  = 0;j < n;j++) dfs(heights,m - 1,j,atl);

        //再提取结果:
        List<List<Integer>> res = new ArrayList<>();
        for(int i = 0;i < m;i++){
            for(int j = 0;j < n;j++){
                if(pac[i][j] && atl[i][j]){
                    List<Integer> temp = new ArrayList<>();
                    temp.add(i);temp.add(j);
                    res.add(temp);
                }
            }
        }
        return res;
    }

    public void dfs(int[][] heights,int i,int j,boolean[][] used){
        used[i][j] = 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 
            && !used[x][y] && heights[x][y] >= heights[i][j]){
                dfs(heights,x,y,used);
            }
        }
    }
}

运行结果:

 

结语: 写博客不仅仅是为了分享学习经历,同时这也有利于我巩固知识点,总结该知识点,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进。同时也希望读者们不吝啬你们的点赞+收藏+关注,你们的鼓励是我创作的最大动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/592148.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

锂电池充放电方式曲线

作为一种“化学能-电能”相互转换的能量装置&#xff0c;锂电池在使用过程中必然会进行充电和放电&#xff0c;合理的充放电方式既能减轻锂电池的损伤程度&#xff0c;又能充分发挥锂电池的性能&#xff0c;具有重要的应用价值。 如《GB/T 31484-2015&#xff1a;电动汽车用动…

非对称齿轮的跨棒距算的对不对

前面有一期咱们聊了非对称齿轮《》&#xff0c;非对称齿轮的齿厚测量一般都为跨棒距。最近研究了下计算方法&#xff0c;对计算结果的正确性做了下验证。 在MATLAB中编制了相关的计算程序&#xff1a; 齿轮的模数4&#xff0c;左侧分度圆压力角25&#xff0c;右侧分度圆压力角…

Sqli-labs第一关到第四关

目录 一&#xff0c;了解PHP源代码 二&#xff0c;破解第一关 2.1在了解完源码之后&#xff0c;我们重点看一下 2.2破解这道题表中有几列 2.3查看表中哪一列有回显 2.4查询库&#xff0c;表&#xff0c;列信息 三&#xff0c;总结 前提&#xff1a; 之所以把1234关…

MySQL基础_1.MySQL概述

文章目录 一、关系型数据库和非关系型数据库1.1 关系型&#xff08;RDBMS&#xff09;1.2 非关系型&#xff08;非RDBMS&#xff09; 二、常用的基础语句2.1 查看表的创建信息2.2 编码问题 一、关系型数据库和非关系型数据库 1.1 关系型&#xff08;RDBMS&#xff09; 是最古…

都上3D数字孪生了,2D的WEB组态和大屏可视化未来的发展在哪里?趋势是基于页面嵌套、蓝图连线等新技术,与功能业务应用融合

首先回顾下组态工具的发展史&#xff1a; 回顾发展史&#xff0c;WEB组态终于可以搭建业务系统了&#xff01;&#xff08;页面嵌套 节点编辑 WEB组态 上位机 大屏可视化 无代码 0代码 iframe nodered 蓝图&#xff09;-CSDN博客文章浏览阅读624次&#xff0c;点赞12次&#x…

ThreeJS:纹理的颜色空间

色彩空间Color Space 在ThreeJS中&#xff0c;纹理的colorSpace属性用于定义文里的颜色空间。 颜色空间是一个用于描述颜色的数学模型&#xff0c;在现实生活中&#xff0c;人眼可以观察到无数种颜色&#xff0c;而颜色空间就是用来描述这些颜色的一个方法&#xff0c;不同的颜…

C语言-自定义类型:结构体,枚举,联合

目录 一、结构体1.1 结构体变量的定义和初始化1.2 结构体内存对齐1.3 修改默认对齐数1.4 结构体传参 二、位段2.1 什么是位段2.2 位段的内存分配2.3 位段的跨平台问题2.4 位段的应用 三、枚举3.1 枚举类型的定义3.2 枚举的优点 四、联合&#xff08;共用体&#xff09;4.1 联合…

c#数据库: 9.删除和添加新字段/数据更新

先把原来数据表的sexy字段删除,然后重新在添加字段sexy,如果添加成功,sexy列的随机内容会更新.原数据表如下: using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Linq; using System.…

Linux理解文件操作 文件描述符fd 理解重定向 dup2 缓冲区 C语言实现自己的shell

文章目录 前言一、文件相关概念与操作1.1 open()1.2 close()1.3 write()1.4 read()1.4 写入的时候先清空文件内容再写入1.5 追加&#xff08;a && a&#xff09; 二、文件描述符2.1 文件描述符 fd 0 1 2 的理解2.2 FILE结构体&#xff1a;的源代码 三、深入理解文件描述…

jupyter notebook 设置密码报错ModuleNotFoundError: No module named ‘notebook.auth‘

jupyter notebook 设置密码报错ModuleNotFoundError: No module named ‘notebook.auth‘ 原因是notebook新版本没有notebook.auth 直接输入以下命令即可设置密码 jupyter notebook password

k8s调度原理以及自定义调度器

kube-scheduler 是 kubernetes 的核心组件之一&#xff0c;主要负责整个集群资源的调度功能&#xff0c;根据特定的调度算法和策略&#xff0c;将 Pod 调度到最优的工作节点上面去&#xff0c;从而更加合理、更加充分的利用集群的资源&#xff0c;这也是我们选择使用 kubernete…

Linux---软硬链接

软链接 我们先学习一下怎样创建软链接文件&#xff0c;指令格式为&#xff1a;ln -s 被链接的文件 生成的链接文件名 我们可以这样记忆&#xff1a;ln是link的简称&#xff0c;s是soft的简称。 我们在下面的图片中就是给test文件生成了一个软链接mytest&#xff1a; 我们来解…

数据结构篇其四---栈:后进先出的魔法世界

前言 栈的学习难度非常简单&#xff0c;前提是如果你学过顺序表和单链表的话&#xff0c;我直接说我的观点了&#xff0c;栈就是有限制的顺序表和单链表。 栈只允许一端进行插入删除。栈去除了各种情况复杂的插入删除&#xff0c;只允许一端插入删除的特性&#xff0c;这一种数…

5月4(信息差)

&#x1f384; HDMI ARC国产双精度浮点dsp杜比数码7.1声道解码AC3/dts/AAC环绕声光纤、同轴、USB输入解码板KC33C &#x1f30d; 国铁集团回应高铁票价将上涨 https://finance.eastmoney.com/a/202405043066422773.html ✨ 源代码管理平台GitLab发布人工智能编程助手DuoCha…

【数据结构】您有一份KMP算法教学已到账,请注意查收!!!

KMP算法 导读一、KMP算法1.1 重要术语1.2 部分匹配值1.3 部分匹配值的作用 二、KMP算法原理2.1 从指针的角度理解KMP算法2.2 从匹配的角度理解KMP算法2.3 小结 三、KMP算法的实现3.1 next数组3.2 next数组的计算3.2.1 通过PM值计算next数组3.2.2 通过移位模拟计算next数组3.2.3…

Web Storage 笔记11 网页数据存储

相关内容&#xff1a;Web Storage基本概念、localStorage、sessionStorage、登录注销实例、…… 在制作网页时会希望记录一些信息&#xff0c;例如用户登录状态、计数器或者小游戏等&#xff0c;但是又不希望用到数据库&#xff0c;就可以利用WebStorage技术将数据存储在用户浏…

Kubelet containerd 管理命令 ctr常用操作

镜像常用操作 1. 拉取镜像 ctr images pull docker.io/library/nginx:alpine 指定平台 --all-platforms&#xff1a;所有平台&#xff08;amd64 、arm、386 、ppc64le 等&#xff09;&#xff0c;不加的话下载当前平台架构 --platform&#xff1a;指定linux/amd64平台 ctr …

鸿蒙开发仿咸鱼TabBar

鸿蒙开发自定义TabBar&#xff0c;实现tabBar 上中间按钮凸起效果 第一步、定义数据模型 export default class TabItemData{defaultIcon: ResourceselectedIcon: Resourcetitle: stringisMiddle: booleanconstructor(defaultIcon:Resource, selectedIcon:Resource, title:st…

并发-启动线程的正确姿势

目录 启动线程的正确姿势 Start方法原理解读 Run方法原理解读 常见问题 启动线程的正确姿势 start()与run()方法的比较测试结果可以看出&#xff0c;runnable.run()方法是由main线程执行的&#xff0c;而要子线程执行就一定要先调用start()启动新线程去执行run方法并不能成…

【数据结构】第四讲:双向链表

目录 一、链表的分类 二、双向链表的结构及实现 1.带头双向链表的结构 2.创建节点 3.初始化 4.尾插 5.打印 6.头插 7.尾删 8.头删 9.在pos位置之后插入数据 10.删除pos节点 11.查找 12.销毁 个人主页&#xff1a;深情秋刀鱼-CSDN博客 数据结构专栏&#xff1a;数…
最新文章