和同事谈谈Flood Fill 算法
前言
今天忙完了公司的工作后,发现同事在做LeeCode的算法题,顿时来了兴趣,于是王子与同事一起探讨如何能做好算法题,今天在此文章中和大家分享一下。
什么是Flood Fill 算法
我们今天谈论的是Flood Fill算法,那么什么是Flood Fill算法呢?
为了理解什么是Flood Fill算法,我们先抛开算法本身的概念,王子给大家说一些平时工作生活中的场景。
1.画图工具的填充功能
相信大家都用过Windows的画图工具,我们看下图

用颜料桶给一个图形区域染色,这就是比较典型的Flood Fill 算法的应用。
2.扫雷游戏

我们玩扫雷游戏的时候,当我们选中一块的时候,会向四周延展出一大片区域,那么有没有想过如果让你来实现这个算法,要怎么实现呢?这也是Flood Fill 算法的实际应用。
3.ps中的魔棒工具

在我们使用魔棒工具抠图的时候,原理其实也是一样的,选中一处后,将相连的相似颜色的部分选中,只不过这里面多了相似颜色的算法。
好了,看完以上的场景,相信小伙伴们对Flood Fill 算法应该有一个概念性的认识了,那么再做这种题之前,我们先考虑一下要做出这种题的套路,也可以说是一种框架思维。
Flood Fill 算法的解题框架
以上所有的例子都可以把它想成在一个图上进行操作,而图又可以叫做二维图形,既然是二维图形就可以给他加上坐标轴,(x,y)来代表一个像素点。
有了上边的想法后,继续思考,其实解决算法无外乎就是递归、遍历。
而这个问题就可以想象成一个4叉树的遍历问题,所以解题框架如下:
// (x, y) 为坐标位置
void fill(int x, int y) {
fill(x - 1, y); // 左
fill(x + 1, y); // 右
fill(x, y - 1); // 下
fill(x, y + 1); // 上
}
这个框架其实很容易理解,对于四叉树结构,或者书二维矩阵的结构,这个框架基本都可以解决,从选中的像素点开始,向四周递归遍历,获得想要的结果。
有了框架的思维,那么我们就去找一道真题来看看吧。
真题解析
今天我们选择的真题是leecode上的733题,图像渲染,题目如下:

下面我们就根据框架思维写一下我们的解题步骤:
class Solution {
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
// 当前颜色
int origColor = image[sr][sc];
fill(image,sr,sc,origColor,newColor);
return image;
}
public void fill(int[][] image, int x, int y, int origColor, int newColor){
// 判断边界超出数组
if(!inArea(image,x,y)){
return;
}
// 判断边界遇到其他颜色
if(image[x][y]!=origColor){
return;
}
// 赋值新的颜色
image[x][y]=newColor;
// 引入框架
fill(image,x-1,y,origColor,newColor);
fill(image,x+1,y,origColor,newColor);
fill(image,x,y-1,origColor,newColor);
fill(image,x,y+1,origColor,newColor);
}
public Boolean inArea(int[][] image, int x, int y){
return x>=0&&x<image.length&&y>=0&&y<image[0].length;
}
}
上边的注释写的已经跟完整了,希望小伙伴们动脑仔细看懂这段代码,看懂后你就明白了如何使用框架来解决问题了。
那么上边的就是正确答案吗,其实这段代码是有问题的,就是如果origColor和newColor如果相同的话,就会导致陷入无限递归。
那么如何解决呢?
最终答案
我们再次阅读以下上边的代码,可以知道出现无限递归的原因是每个坐标都要搜索上下左右,那么对于一个坐标,一定也会被上下左右的坐标多次重复搜索。所以我们必须保证重复搜索时能正确退出递归
其实针对于上边提到的这种情况,我们可以这样想,如果我们将元素的搜索路径记录下来,每次搜索时如果发现之前已经走过了这条路,那么就return,这样不就行了吗。
于是有了下边的最终答案
class Solution {
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
// 当前颜色
int origColor = image[sr][sc];
fill(image,sr,sc,origColor,newColor);
return image;
}
public void fill(int[][] image, int x, int y, int origColor, int newColor){
// 判断边界超出数组
if(!inArea(image,x,y)){
return;
}
// 判断边界遇到其他颜色
if(image[x][y]!=origColor){
return;
}
// 已经探索的origColor区域
if(image[x][y]==-1){
return;
}
// 赋值新的颜色之前先打一个标记,证明已经探索过
image[x][y]=-1;
// 引入框架
fill(image,x-1,y,origColor,newColor);
fill(image,x+1,y,origColor,newColor);
fill(image,x,y-1,origColor,newColor);
fill(image,x,y+1,origColor,newColor);
// 全部递归后,再把标记赋值成新的颜色
image[x][y]=newColor;
}
public Boolean inArea(int[][] image, int x, int y){
return x>=0&&x<image.length&&y>=0&&y<image[0].length;
}
}
为什么要用-1做标记呢,其实这个无所谓,只要是一个题目中不会使用到的值就可以了(题目中规定值在0-65535之间)
扩展算法
小伙伴们,上边的算法题如果已经弄明白了,那我们想一想,如果要实现PS的魔棒工具要怎么实现呢,我们来分析一下。
魔棒工具和上边的算法其实原理是一样的,只不过有两点不同,一是选择区域时选择的不是相同颜色,而是相似颜色,在ps中是可以根据阈值来设定相似程度的;二是使用魔棒选择区域后,在边界上会有边框,说明选中了哪些地方,这就变成了填充边界问题。
对于第一个问题很好解决,只要做如下改动即可
//原
// 判断边界遇到其他颜色
if(image[x][y]!=origColor){
return;
} //新
// 遇到其他不在阈值内的颜色 threshold代表阈值
if (Math.abs(image[x][y] - origColor) > threshold){
return;
}
对于第二个问题,首先明确,我们要染色的部分只是边界,而不是内部区域,思考一下,内部区域的每个像素点的四周都是相同的颜色,而临近边界的像素点至少有一个方向的颜色与其不同,这就是解决问题的方案。这次我们使用visited数组来存储搜索路径,代码如下
class Solution {
boolean[][] visited = null;
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
// 当前颜色
int origColor = image[sr][sc];
// 初始化访问路径数组
visited = new boolean[image.length][image[0].length];
fill(image,sr,sc,origColor,newColor);
return image;
}
public int fill(int[][] image, int x, int y, int origColor, int newColor){
// 判断边界超出数组
if(!inArea(image,x,y)){
return 0;
}
// 判断边界遇到其他颜色
if(image[x][y]!=origColor){
return 0;
}
// 已经探索的origColor区域
if(visited[x][y]){
return 1;
}
// 赋值新的颜色之前先打一个标记,证明已经探索过
visited[x][y]=true;
// 引入框架
int result=
fill(image,x-1,y,origColor,newColor)+
fill(image,x+1,y,origColor,newColor)+
fill(image,x,y-1,origColor,newColor)+
fill(image,x,y+1,origColor,newColor);
// 全部递归后,如果result<4代表像素点是边界,赋值颜色
if(result<4){
image[x][y]=newColor;
}
return 1;
}
public Boolean inArea(int[][] image, int x, int y){
return x>=0&&x<image.length&&y>=0&&y<image[0].length;
}
}
总结
以上详解了Flood Fill 算法解决的题目,其实掌握了这种框架思维,类似的题目都可以想出解决思路。
我们可以这么认为,做算法题就是要找到做题的套路,就像我们上学时做数学题,在其中不也是有些固定的套路吗。
leecode上边的1034题也是类似的思路,小伙伴们可以自己去试试怎么解决,欢迎留言一起讨论。
之后的文章,我也会不定期的分享一些有关算法刷题的解题方案,和小伙伴们一起研究解题套路,毕竟有些公司还是会考一些算法的嘛。
好了本文就到这里,欢迎大家持续关注!
往期文章推荐:
中间件专辑:

和同事谈谈Flood Fill 算法的更多相关文章
- 带你学习Flood Fill算法与最短路模型
一.Flood Fill(连通块问题) 0.简介 Flood Fill(洪水覆盖) 可以在线性的时间复杂内,找到某个点所在的连通块! 注:基于宽搜的思想,深搜也可以做但可能会爆栈 flood fill ...
- 图像处理之泛洪填充算法(Flood Fill Algorithm)
泛洪填充算法(Flood Fill Algorithm) 泛洪填充算法又称洪水填充算法是在很多图形绘制软件中常用的填充算法,最熟悉不过就是 windows paint的油漆桶功能.算法的原理很简单,就 ...
- 图像处理------泛洪填充算法(Flood Fill Algorithm) 油漆桶功能
泛洪填充算法(Flood Fill Algorithm) 泛洪填充算法又称洪水填充算法是在很多图形绘制软件中常用的填充算法,最熟悉不过就是 windows paint的油漆桶功能.算法的原理很简单,就 ...
- 733. Flood Fill 简单型染色问题
[抄题]: An image is represented by a 2-D array of integers, each integer representing the pixel value ...
- SSOJ 2316 面积【DFS/Flood Fill】
题目描述 编程计算由“1”号围成的下列图形的面积.面积计算方法是统计1号所围成的闭合曲线中点的数目. 如图所示,在10*10的二维数组中,“1”围住了15个点,因此面积为15. 题目大意:对于给定的1 ...
- LN : leetcode 733 Flood Fill
lc 733 Flood Fill 733 Flood Fill An image is represented by a 2-D array of integers, each integer re ...
- 【Leetcode_easy】733. Flood Fill
problem 733. Flood Fill 题意:图像处理中的泛洪填充算法,常见的有四邻域像素填充法.八邻域像素填充法.基于扫描线的像素填充法,实现方法分为递归与非递归(基于栈). 泛洪填充算法原 ...
- [LeetCode] Flood Fill 洪水填充
An image is represented by a 2-D array of integers, each integer representing the pixel value of the ...
- [Swift]LeetCode733. 图像渲染 | Flood Fill
An image is represented by a 2-D array of integers, each integer representing the pixel value of the ...
随机推荐
- 当asp.net core偶遇docker一(模型验证和Rabbitmq 一)
比如我们有一些设计,依赖于某些软件,比如rabbitmq 当管理员功能,反复错误三五次之后,就发送一条消息到队列里去,我们又不希望对原先设计带来侵入式的改变业务 这个时候,我们就可以在模型验证里面加入 ...
- 为什么需要将网站封装为app?
网站封装为app是一种宝贵的资源,为客户提供稳定的平台,一个网站也是一个有效的工具,用于企业与其客户之间的通信.企业网站用户可以通过他们的笔记本电脑,台式机,平板电脑,智能手机以及带有浏览器设备的 ...
- “随手记”开发记录day02
今天完成了 向瑜- 布局: 1.修改日期(√) 2.选择分类(√) 3.输入金额(√) 赵常恒- 1.登录,注册页面布局(√) 刘志霄- 1.个人信息页面规划(√)
- application.yml使用@符合问题:'@' that cannot start any token. (Do not use @ for indentation)
在application配置文件中使用@出现异常: Exception in thread "main" while scanning for the next tokenfoun ...
- JS 获取验证码按钮改变案例
HTML代码 <div class="box"> <label for="">手机号</label> <input t ...
- data argumentation 数据增强汇总
几何变换 flip:水平翻转,也叫镜像:垂直翻转 rotation:图片旋转一定的角度,这个可以通过opencv来操作,各个框架也有自己的算子 crop:随机裁剪,比如说,在ImageNet中可以将输 ...
- Java Redis系列1 关系型数据库与非关系型数据库的优缺点及概念
Java Redis系列1 关系型数据库与非关系型数据库的优缺点及概念 在学习redis之前我们先来学习两个概念,即什么是关系型数据库什么是非关系型数据库,二者的区别是什么,二者的关系又是什么? ** ...
- python处理转载博客html
前景 在转载别人博客的时候通常我们会通过复制html然后放到编辑器里面, 但是通常html里有很多杂七杂八的东西, 比如script, svg这些标签导致排版出现问题 例如由lu标签引起的 由svg标 ...
- golang中type关键字使用
type关键字使用 type是go语法里的重要而且常用的关键字,type绝不只是对应于C/C++中的typedef.搞清楚type的使用,就容易理解go语言中的核心概念struct.interface ...
- 这都Java15了,Java7特性还没整明白?
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...